@thejob/schema 2.1.9 → 2.3.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/dist/index.cjs CHANGED
@@ -32,22 +32,29 @@ var index_exports = {};
32
32
  __export(index_exports, {
33
33
  AnswerChoiceType: () => AnswerChoiceType,
34
34
  ApplicationReceivePreference: () => ApplicationReceivePreference,
35
- BindingKind: () => BindingKind,
36
- BindingSchema: () => BindingSchema,
35
+ BillingPeriod: () => BillingPeriod,
36
+ BillingSubscriptionSchema: () => BillingSubscriptionSchema,
37
+ BillingTransactionSchema: () => BillingTransactionSchema,
37
38
  CAMPAIGN_MAX_RECIPIENTS_CEILING: () => CAMPAIGN_MAX_RECIPIENTS_CEILING,
38
- CampaignAnchorField: () => CampaignAnchorField,
39
+ CONSUME_MAX_UNITS: () => CONSUME_MAX_UNITS,
40
+ CampaignDataSourceSchema: () => CampaignDataSourceSchema,
39
41
  CampaignEndKind: () => CampaignEndKind,
40
42
  CampaignIntervalUnit: () => CampaignIntervalUnit,
41
43
  CampaignSchema: () => CampaignSchema,
42
44
  CampaignStatus: () => CampaignStatus,
45
+ CampaignStatusTransitions: () => CampaignStatusTransitions,
43
46
  CampaignTriggerKind: () => CampaignTriggerKind,
44
47
  CampaignTriggerSchema: () => CampaignTriggerSchema,
45
48
  ChoiceQuestionSchema: () => ChoiceQuestionSchema,
49
+ CityFilterSchema: () => CityFilterSchema,
50
+ CitySchema: () => CitySchema,
46
51
  Common: () => Common,
47
52
  CompensationType: () => CompensationType,
48
53
  CompletenessScoreSchema: () => CompletenessScoreSchema,
49
54
  ContactTypes: () => ContactTypes,
50
55
  CoordinatorPageLinkSchema: () => CoordinatorPageLinkSchema,
56
+ CountryFilterSchema: () => CountryFilterSchema,
57
+ CountrySchema: () => CountrySchema,
51
58
  DISPLAY_DATE_FORMAT: () => DISPLAY_DATE_FORMAT,
52
59
  DISPLAY_DATE_FORMAT_SHORT: () => DISPLAY_DATE_FORMAT_SHORT,
53
60
  DateStringSchema: () => DateStringSchema,
@@ -61,12 +68,29 @@ __export(index_exports, {
61
68
  EducationSchema: () => EducationSchema,
62
69
  EmployeeCount: () => EmployeeCount,
63
70
  EmploymentType: () => EmploymentType,
71
+ EntitlementConsumeSchema: () => EntitlementConsumeSchema,
72
+ EntitlementDenialReason: () => EntitlementDenialReason,
73
+ EntitlementDenialSchema: () => EntitlementDenialSchema,
74
+ EntitlingSubscriptionStatuses: () => EntitlingSubscriptionStatuses,
64
75
  ExperienceLevel: () => ExperienceLevel,
65
76
  FEEDBACK_MESSAGE_MAX: () => FEEDBACK_MESSAGE_MAX,
77
+ FeatureEntitlementSchema: () => FeatureEntitlementSchema,
78
+ FeatureKey: () => FeatureKey,
79
+ FeedbackFilterSchema: () => FeedbackFilterSchema,
66
80
  FeedbackSchema: () => FeedbackSchema,
67
81
  FeedbackStatus: () => FeedbackStatus,
68
82
  FeedbackType: () => FeedbackType,
83
+ GEO_COLLECTIONS: () => GEO_COLLECTIONS,
84
+ GEO_SEARCH_MAX: () => GEO_SEARCH_MAX,
85
+ GRANT_MAX_LIMIT: () => GRANT_MAX_LIMIT,
69
86
  GeneraDetailFields: () => GeneraDetailFields,
87
+ GeoKind: () => GeoKind,
88
+ GeoSyncPhase: () => GeoSyncPhase,
89
+ GeoSyncRunSchema: () => GeoSyncRunSchema,
90
+ GeoSyncStatus: () => GeoSyncStatus,
91
+ GeoSyncTrigger: () => GeoSyncTrigger,
92
+ GrantType: () => GrantType,
93
+ GroupFilterSchema: () => GroupFilterSchema,
70
94
  GroupManagedBy: () => GroupManagedBy,
71
95
  GroupMembershipSchema: () => GroupMembershipSchema,
72
96
  GroupMembershipStatus: () => GroupMembershipStatus,
@@ -78,31 +102,41 @@ __export(index_exports, {
78
102
  JOB_TAXONOMY: () => JOB_TAXONOMY,
79
103
  JobAlertFrequency: () => JobAlertFrequency,
80
104
  JobCategory: () => JobCategory,
105
+ JobFilterSchema: () => JobFilterSchema,
81
106
  JobIndustry: () => JobIndustry,
82
107
  JobSchema: () => JobSchema,
83
108
  JobSearchUrgency: () => JobSearchUrgency,
84
109
  JobStatus: () => JobStatus,
85
110
  JobSubCategory: () => JobSubCategory,
86
- ListFilterBranch: () => ListFilterBranch,
87
- ListFilterBranchSchema: () => ListFilterBranchSchema,
88
- ListFilterSchema: () => ListFilterSchema,
111
+ LanguageFilterSchema: () => LanguageFilterSchema,
89
112
  LocationSchema: () => LocationSchema,
90
113
  MIN_SALARY_LOWER_BOUND: () => MIN_SALARY_LOWER_BOUND,
91
114
  MIN_SALARY_UPPER_BOUND: () => MIN_SALARY_UPPER_BOUND,
115
+ MailingListFilterSchema: () => MailingListFilterSchema,
92
116
  MailingListSchema: () => MailingListSchema,
93
- NotificationChannel: () => NotificationChannel,
117
+ MoneySchema: () => MoneySchema,
118
+ NotificationType: () => NotificationType,
94
119
  OccurrenceStatus: () => OccurrenceStatus,
95
120
  PREFILL_MIRRORED_KEYS: () => PREFILL_MIRRORED_KEYS,
96
121
  PRIVATE_PROFILE_FIELDS: () => PRIVATE_PROFILE_FIELDS,
122
+ PaddleEnvironment: () => PaddleEnvironment,
123
+ PaddlePriceIdsSchema: () => PaddlePriceIdsSchema,
124
+ PageFilterSchema: () => PageFilterSchema,
97
125
  PageSchema: () => PageSchema,
98
126
  PageStatus: () => PageStatus,
99
127
  PageType: () => PageType,
100
128
  PaginationSchema: () => PaginationSchema,
129
+ PlanGrantSchema: () => PlanGrantSchema,
130
+ PlanKind: () => PlanKind,
131
+ PlanSchema: () => PlanSchema,
132
+ PlanStatus: () => PlanStatus,
101
133
  PostSchema: () => PostSchema,
102
134
  PostStatus: () => PostStatus,
103
135
  ProficiencyLevel: () => ProficiencyLevel,
136
+ PushSubscriptionSchema: () => PushSubscriptionSchema,
104
137
  QuestionSchema: () => QuestionSchema,
105
138
  QuestionType: () => QuestionType,
139
+ QuotaPeriod: () => QuotaPeriod,
106
140
  ReadAndAcknowledgeQuestionSchema: () => ReadAndAcknowledgeQuestionSchema,
107
141
  RecruiterPageLinkSchema: () => RecruiterPageLinkSchema,
108
142
  ReferralSource: () => ReferralSource,
@@ -114,15 +148,18 @@ __export(index_exports, {
114
148
  SavedSearchSchema: () => SavedSearchSchema,
115
149
  SavedSearchStatus: () => SavedSearchStatus,
116
150
  SignupContextSource: () => SignupContextSource,
151
+ SkillFilterSchema: () => SkillFilterSchema,
117
152
  SkillSchema: () => SkillSchema,
118
153
  SocialAccount: () => SocialAccount,
119
154
  SocialAccountSchema: () => SocialAccountSchema,
155
+ StateFilterSchema: () => StateFilterSchema,
156
+ StateSchema: () => StateSchema,
120
157
  StudentCompletenessSchema: () => StudentCompletenessSchema,
121
158
  StudyType: () => StudyType,
159
+ SubscriptionStatus: () => SubscriptionStatus,
122
160
  SupportedAnswerChoiceTypes: () => SupportedAnswerChoiceTypes,
123
161
  SupportedApplicationReceivePreferences: () => SupportedApplicationReceivePreferences,
124
- SupportedBindingKinds: () => SupportedBindingKinds,
125
- SupportedCampaignAnchorFields: () => SupportedCampaignAnchorFields,
162
+ SupportedBillingPeriods: () => SupportedBillingPeriods,
126
163
  SupportedCampaignEndKinds: () => SupportedCampaignEndKinds,
127
164
  SupportedCampaignIntervalUnits: () => SupportedCampaignIntervalUnits,
128
165
  SupportedCampaignStatuses: () => SupportedCampaignStatuses,
@@ -132,22 +169,37 @@ __export(index_exports, {
132
169
  SupportedEducationLevels: () => SupportedEducationLevels,
133
170
  SupportedEmployeeCounts: () => SupportedEmployeeCounts,
134
171
  SupportedEmploymentTypes: () => SupportedEmploymentTypes,
172
+ SupportedEntitlementDenialReasons: () => SupportedEntitlementDenialReasons,
135
173
  SupportedExperienceLevels: () => SupportedExperienceLevels,
174
+ SupportedFeatureKeys: () => SupportedFeatureKeys,
136
175
  SupportedFeedbackStatuses: () => SupportedFeedbackStatuses,
137
176
  SupportedFeedbackTypes: () => SupportedFeedbackTypes,
177
+ SupportedGeoKinds: () => SupportedGeoKinds,
178
+ SupportedGeoSyncPhases: () => SupportedGeoSyncPhases,
179
+ SupportedGeoSyncStatuses: () => SupportedGeoSyncStatuses,
180
+ SupportedGeoSyncTriggers: () => SupportedGeoSyncTriggers,
181
+ SupportedGrantTypes: () => SupportedGrantTypes,
182
+ SupportedGroupManagedBy: () => SupportedGroupManagedBy,
183
+ SupportedGroupMembershipStatuses: () => SupportedGroupMembershipStatuses,
184
+ SupportedGroupStatuses: () => SupportedGroupStatuses,
185
+ SupportedGroupVisibilities: () => SupportedGroupVisibilities,
138
186
  SupportedJobAlertFrequencies: () => SupportedJobAlertFrequencies,
139
187
  SupportedJobCategories: () => SupportedJobCategories,
140
188
  SupportedJobIndustries: () => SupportedJobIndustries,
141
189
  SupportedJobSearchUrgencies: () => SupportedJobSearchUrgencies,
142
190
  SupportedJobStatuses: () => SupportedJobStatuses,
143
191
  SupportedJobSubCategories: () => SupportedJobSubCategories,
144
- SupportedNotificationChannels: () => SupportedNotificationChannels,
192
+ SupportedNotificationTypes: () => SupportedNotificationTypes,
145
193
  SupportedOccurrenceStatuses: () => SupportedOccurrenceStatuses,
194
+ SupportedPaddleEnvironments: () => SupportedPaddleEnvironments,
146
195
  SupportedPageStatuses: () => SupportedPageStatuses,
147
196
  SupportedPageTypes: () => SupportedPageTypes,
197
+ SupportedPlanKinds: () => SupportedPlanKinds,
198
+ SupportedPlanStatuses: () => SupportedPlanStatuses,
148
199
  SupportedPostStatuses: () => SupportedPostStatuses,
149
200
  SupportedProficiencyLevels: () => SupportedProficiencyLevels,
150
201
  SupportedQuestionTypes: () => SupportedQuestionTypes,
202
+ SupportedQuotaPeriods: () => SupportedQuotaPeriods,
151
203
  SupportedReferralSources: () => SupportedReferralSources,
152
204
  SupportedReportReasons: () => SupportedReportReasons,
153
205
  SupportedResourceTypes: () => SupportedResourceTypes,
@@ -156,17 +208,26 @@ __export(index_exports, {
156
208
  SupportedSignupContextSources: () => SupportedSignupContextSources,
157
209
  SupportedSocialAccounts: () => SupportedSocialAccounts,
158
210
  SupportedStudyTypes: () => SupportedStudyTypes,
211
+ SupportedSubscriptionStatuses: () => SupportedSubscriptionStatuses,
212
+ SupportedTemplateVariableBindingKinds: () => SupportedTemplateVariableBindingKinds,
213
+ SupportedTransactionStatuses: () => SupportedTransactionStatuses,
159
214
  SupportedUserProfileVisibilities: () => SupportedUserProfileVisibilities,
160
215
  SupportedUserRoles: () => SupportedUserRoles,
161
216
  SupportedUserStatuses: () => SupportedUserStatuses,
162
217
  SupportedWorkModes: () => SupportedWorkModes,
163
218
  TAXONOMY_LABELS: () => TAXONOMY_LABELS,
219
+ TemplateVariableBindingKind: () => TemplateVariableBindingKind,
220
+ TemplateVariableBindingSchema: () => TemplateVariableBindingSchema,
221
+ TerminalCampaignStatuses: () => TerminalCampaignStatuses,
222
+ TerminalGeoSyncStatuses: () => TerminalGeoSyncStatuses,
164
223
  TermsAcceptedSchema: () => TermsAcceptedSchema,
224
+ TransactionStatus: () => TransactionStatus,
165
225
  UserAdditionalInfoSchema: () => UserAdditionalInfoSchema,
166
226
  UserCertificationSchema: () => UserCertificationSchema,
167
227
  UserCompletenessSchema: () => UserCompletenessSchema,
168
228
  UserCoordinatorProfileSchema: () => UserCoordinatorProfileSchema,
169
229
  UserDetailType: () => UserDetailType,
230
+ UserFilterSchema: () => UserFilterSchema,
170
231
  UserGeneralDetailSchema: () => UserGeneralDetailSchema,
171
232
  UserIdAndCreatedAtSchema: () => UserIdAndCreatedAtSchema,
172
233
  UserInterestSchema: () => UserInterestSchema,
@@ -181,14 +242,26 @@ __export(index_exports, {
181
242
  UserStatus: () => UserStatus,
182
243
  WorkExperienceSchema: () => WorkExperienceSchema,
183
244
  WorkMode: () => WorkMode,
245
+ canTransitionCampaign: () => canTransitionCampaign,
184
246
  categoryOfIndustry: () => categoryOfIndustry,
185
247
  categoryOfSubCategory: () => categoryOfSubCategory,
248
+ codeTokens: () => codeTokens,
249
+ conjunction: () => conjunction,
250
+ currencyBound: () => currencyBound,
186
251
  dateString: () => dateString,
252
+ days: () => days,
187
253
  deriveIndustry: () => deriveIndustry,
254
+ enumFacet: () => enumFacet,
255
+ epoch: () => epoch,
188
256
  getSchemaByQuestion: () => getSchemaByQuestion,
189
257
  industriesOfCategory: () => industriesOfCategory,
190
258
  industryOfSubCategory: () => industryOfSubCategory,
191
- subCategoriesOfIndustry: () => subCategoriesOfIndustry
259
+ months: () => months,
260
+ percent: () => percent,
261
+ qualified: () => qualified,
262
+ rejectUnknownKeys: () => rejectUnknownKeys,
263
+ subCategoriesOfIndustry: () => subCategoriesOfIndustry,
264
+ tokens: () => tokens
192
265
  });
193
266
  module.exports = __toCommonJS(index_exports);
194
267
 
@@ -214,6 +287,102 @@ var UserIdAndCreatedAtSchema = (0, import_yup.object)().shape({
214
287
  createdAt: (0, import_yup.string)().required().label("Created At")
215
288
  });
216
289
 
290
+ // src/common/facet.schema.ts
291
+ var import_yup2 = require("yup");
292
+ var tokens = (label) => (0, import_yup2.array)().of((0, import_yup2.string)().trim().required()).optional().default([]).label(label);
293
+ var codeTokens = (label) => (0, import_yup2.array)().of((0, import_yup2.string)().trim().uppercase().required()).optional().default([]).label(label);
294
+ var enumFacet = (values, label) => (0, import_yup2.array)().of((0, import_yup2.string)().oneOf(values).required()).optional().default([]).label(label);
295
+ var days = (label) => (0, import_yup2.number)().integer().min(0).max(3650).optional().default(void 0).label(label);
296
+ var months = (label) => (0, import_yup2.number)().integer().min(0).max(1200).optional().default(void 0).label(label);
297
+ var percent = (label) => (0, import_yup2.number)().integer().min(0).max(100).optional().default(void 0).label(label);
298
+ var epoch = (label) => (0, import_yup2.number)().integer().min(0).optional().default(void 0).label(label).meta({ widget: "date" });
299
+ var currencyBound = (label, currencyField) => (0, import_yup2.number)().integer().min(0).optional().default(void 0).label(label).when(currencyField, {
300
+ is: (v) => !Array.isArray(v) || v.length !== 1,
301
+ then: (s) => s.test(
302
+ "requires-single-currency",
303
+ "Pick exactly one salary currency to filter by salary",
304
+ (value) => value === void 0 || value === null
305
+ )
306
+ });
307
+ var qualified = (qualifierValues, qualifierKey, label) => (0, import_yup2.array)().of(
308
+ // `rejectUnknownKeys`, not `.noUnknown()`, for the reason spelled out on
309
+ // that helper: outside strict mode `.noUnknown()` silently STRIPS an
310
+ // unknown key rather than failing, so a misspelled qualifier would arrive
311
+ // as a name-only entry and quietly widen the match instead of erroring.
312
+ rejectUnknownKeys(
313
+ (0, import_yup2.object)({
314
+ name: (0, import_yup2.string)().trim().required().label("Name"),
315
+ // `"open"` for a free-text qualifier (a certification's issuing body).
316
+ // An empty array would NOT mean "anything goes" — `.oneOf([])` rejects
317
+ // every value — so the open case is named rather than inferred from an
318
+ // empty vocabulary.
319
+ [qualifierKey]: qualifierValues === "open" ? (0, import_yup2.string)().trim().optional().default(void 0).label(label) : (0, import_yup2.string)().oneOf(qualifierValues).optional().default(void 0).label(label)
320
+ }).label(label).required(),
321
+ () => ["name", qualifierKey]
322
+ )
323
+ ).optional().default([]).label(label);
324
+ var conjunctionFieldName = (f) => typeof f === "string" ? f : f.name;
325
+ var conjunction = (fields, label) => {
326
+ const names = fields.map(conjunctionFieldName);
327
+ const isBoolean = (name) => fields.some(
328
+ (f) => typeof f !== "string" && f.name === name && f.type === "boolean"
329
+ );
330
+ return (0, import_yup2.array)().of(
331
+ rejectUnknownKeys(
332
+ (0, import_yup2.object)(
333
+ Object.fromEntries(
334
+ fields.map((f) => {
335
+ const name = conjunctionFieldName(f);
336
+ if (typeof f !== "string" && f.type === "boolean") {
337
+ return [name, (0, import_yup2.boolean)().optional().default(void 0).label(name)];
338
+ }
339
+ const base = (0, import_yup2.string)().trim().optional().default(void 0).label(name);
340
+ return [
341
+ name,
342
+ typeof f !== "string" && f.oneOf ? base.oneOf([...f.oneOf, void 0]) : base
343
+ ];
344
+ })
345
+ )
346
+ ).test(
347
+ "at-least-one-field",
348
+ // Names the unknown key when that is the real problem. A misspelled
349
+ // field (`companyName`) is stripped before this test sees it, so it
350
+ // would otherwise report "needs at least one field set" — true, but
351
+ // it sends the reader looking in the wrong place.
352
+ ({ value }) => {
353
+ const unknown = Object.keys(
354
+ value ?? {}
355
+ ).filter((k) => !names.includes(k));
356
+ return unknown.length ? `${label} has unsupported keys: ${unknown.join(", ")}` : `${label} needs at least one of: ${names.join(", ")}`;
357
+ },
358
+ (value) => value == null || names.some((f) => {
359
+ const v = value[f];
360
+ if (isBoolean(f)) return typeof v === "boolean";
361
+ return typeof v === "string" && v.trim() !== "";
362
+ })
363
+ ).label(label).required(),
364
+ () => names
365
+ )
366
+ ).optional().default([]).label(label);
367
+ };
368
+ var rejectUnknownKeys = (schema, allowed) => schema.test(
369
+ "no-unknown-keys",
370
+ ({ value, label }) => {
371
+ const known = new Set(allowed());
372
+ const bad = Object.keys(value ?? {}).filter(
373
+ (k) => !known.has(k)
374
+ );
375
+ return `${label} has unsupported keys: ${bad.join(", ")}`;
376
+ },
377
+ (value) => {
378
+ if (value == null) return true;
379
+ const known = new Set(allowed());
380
+ return Object.keys(value).every(
381
+ (k) => known.has(k)
382
+ );
383
+ }
384
+ );
385
+
217
386
  // src/common/common.constant.ts
218
387
  var Common = /* @__PURE__ */ ((Common2) => {
219
388
  Common2["Yes"] = "yes";
@@ -270,12 +439,12 @@ var ExperienceLevel = /* @__PURE__ */ ((ExperienceLevel2) => {
270
439
  var SupportedExperienceLevels = Object.values(ExperienceLevel);
271
440
 
272
441
  // src/duration/duration.schema.ts
273
- var import_yup3 = require("yup");
442
+ var import_yup4 = require("yup");
274
443
 
275
444
  // src/extensions/date-string.extension.ts
276
445
  var import_dayjs = __toESM(require("dayjs"), 1);
277
- var import_yup2 = require("yup");
278
- var DateStringSchema = class extends import_yup2.MixedSchema {
446
+ var import_yup3 = require("yup");
447
+ var DateStringSchema = class extends import_yup3.MixedSchema {
279
448
  constructor() {
280
449
  super({
281
450
  type: "mixed",
@@ -325,11 +494,11 @@ var DurationSchema = (options) => {
325
494
  const { startKey = "startDate", endKey = "endDate", format } = options || {};
326
495
  const startLabel = options?.startLabel || startKey;
327
496
  const endLabel = options?.endLabel || endKey;
328
- return (0, import_yup3.object)().shape({
497
+ return (0, import_yup4.object)().shape({
329
498
  [startKey]: dateString().format(format, `${startLabel} must be in the format ${format}`).required(`${startLabel} is required`).typeError(`${startLabel} must be a valid date`),
330
499
  [endKey]: dateString().format(format, `${endLabel} must be in the format ${format}`).when("isActive", {
331
500
  is: (val) => Boolean(val) === false,
332
- then: (schema) => schema.typeError(`${endLabel} must be a valid date`).min((0, import_yup3.ref)(startKey), `${endLabel} must be after ${startKey}`).required(`${endLabel} is required`),
501
+ then: (schema) => schema.typeError(`${endLabel} must be a valid date`).min((0, import_yup4.ref)(startKey), `${endLabel} must be after ${startKey}`).required(`${endLabel} is required`),
333
502
  otherwise: (schema) => schema.nullable().default(null).label(endLabel)
334
503
  // .test(
335
504
  // "notAllowed",
@@ -337,12 +506,12 @@ var DurationSchema = (options) => {
337
506
  // (value) => !value
338
507
  // ),ccc
339
508
  }),
340
- isActive: (0, import_yup3.boolean)().optional().default(false).typeError("isActive must be a boolean value")
509
+ isActive: (0, import_yup4.boolean)().optional().default(false).typeError("isActive must be a boolean value")
341
510
  });
342
511
  };
343
512
 
344
513
  // src/group/group.schema.ts
345
- var import_yup4 = require("yup");
514
+ var import_yup5 = require("yup");
346
515
 
347
516
  // src/group/group.constant.ts
348
517
  var GroupVisibility = /* @__PURE__ */ ((GroupVisibility2) => {
@@ -366,80 +535,129 @@ var GroupStatus = /* @__PURE__ */ ((GroupStatus2) => {
366
535
  GroupStatus2["Archived"] = "archived";
367
536
  return GroupStatus2;
368
537
  })(GroupStatus || {});
538
+ var SupportedGroupStatuses = Object.values(GroupStatus);
539
+ var SupportedGroupVisibilities = Object.values(GroupVisibility);
540
+ var SupportedGroupManagedBy = Object.values(GroupManagedBy);
541
+ var SupportedGroupMembershipStatuses = Object.values(
542
+ GroupMembershipStatus
543
+ );
369
544
 
370
545
  // src/group/group.schema.ts
371
- var GroupTranslationSchema = (0, import_yup4.object)({
372
- language: (0, import_yup4.string)().required("Language is required"),
373
- name: (0, import_yup4.string)().required("Name is required"),
374
- description: (0, import_yup4.string)().optional(),
375
- slug: (0, import_yup4.string)().optional(),
376
- seo: (0, import_yup4.object)({
377
- description: (0, import_yup4.string)().optional(),
378
- keywords: (0, import_yup4.array)().of((0, import_yup4.string)().required()).optional()
546
+ var GroupTranslationSchema = (0, import_yup5.object)({
547
+ language: (0, import_yup5.string)().required("Language is required"),
548
+ name: (0, import_yup5.string)().required("Name is required"),
549
+ description: (0, import_yup5.string)().optional(),
550
+ slug: (0, import_yup5.string)().optional(),
551
+ seo: (0, import_yup5.object)({
552
+ description: (0, import_yup5.string)().optional(),
553
+ keywords: (0, import_yup5.array)().of((0, import_yup5.string)().required()).optional()
379
554
  }).optional()
380
555
  });
381
- var GroupSchema = (0, import_yup4.object)().shape({
382
- name: (0, import_yup4.string)().required().min(3).max(100).label("Name"),
383
- description: (0, import_yup4.string)().required().min(10).max(1e3).label("Description"),
384
- slug: (0, import_yup4.string)().required().label("Slug"),
385
- banner: (0, import_yup4.string)().label("Banner"),
386
- logo: (0, import_yup4.object)().shape({
387
- light: (0, import_yup4.string)().required().label("Light Logo"),
388
- dark: (0, import_yup4.string)().optional().nullable().label("Dark Logo")
556
+ var GroupSchema = (0, import_yup5.object)().shape({
557
+ name: (0, import_yup5.string)().required().min(3).max(100).label("Name"),
558
+ description: (0, import_yup5.string)().required().min(10).max(1e3).label("Description"),
559
+ slug: (0, import_yup5.string)().required().label("Slug"),
560
+ banner: (0, import_yup5.string)().label("Banner"),
561
+ logo: (0, import_yup5.object)().shape({
562
+ light: (0, import_yup5.string)().required().label("Light Logo"),
563
+ dark: (0, import_yup5.string)().optional().nullable().label("Dark Logo")
389
564
  }).optional().nullable().default(null).label("Logo"),
390
- isFeatured: (0, import_yup4.boolean)().label("Is Featured"),
391
- isDiscoverable: (0, import_yup4.boolean)().default(true).label("Is Discoverable"),
392
- featuredMarkets: (0, import_yup4.array)().of((0, import_yup4.string)()).optional().default([]).label("Featured Markets"),
393
- followersCount: (0, import_yup4.number)().min(0).label("Followers Count"),
394
- membersCount: (0, import_yup4.number)().min(0).label("Members Count"),
395
- visibility: (0, import_yup4.mixed)().oneOf(Object.values(GroupVisibility)).default("public" /* Public */).label("Visibility"),
396
- groupRules: (0, import_yup4.string)().optional().label("Group Rules"),
397
- countries: (0, import_yup4.array)().of((0, import_yup4.string)()).optional().label("Countries"),
398
- filterQuery: (0, import_yup4.object)().shape({
399
- jobs: (0, import_yup4.string)().label("Job"),
400
- courses: (0, import_yup4.string)().label("Course"),
401
- discussions: (0, import_yup4.string)().label("Discussion")
565
+ isFeatured: (0, import_yup5.boolean)().label("Is Featured"),
566
+ isDiscoverable: (0, import_yup5.boolean)().default(true).label("Is Discoverable"),
567
+ featuredMarkets: (0, import_yup5.array)().of((0, import_yup5.string)()).optional().default([]).label("Featured Markets"),
568
+ followersCount: (0, import_yup5.number)().min(0).label("Followers Count"),
569
+ membersCount: (0, import_yup5.number)().min(0).label("Members Count"),
570
+ visibility: (0, import_yup5.mixed)().oneOf(Object.values(GroupVisibility)).default("public" /* Public */).label("Visibility"),
571
+ groupRules: (0, import_yup5.string)().optional().label("Group Rules"),
572
+ countries: (0, import_yup5.array)().of((0, import_yup5.string)()).optional().label("Countries"),
573
+ filterQuery: (0, import_yup5.object)().shape({
574
+ jobs: (0, import_yup5.string)().label("Job"),
575
+ courses: (0, import_yup5.string)().label("Course"),
576
+ discussions: (0, import_yup5.string)().label("Discussion")
402
577
  }).label("Filter Query"),
403
- seo: (0, import_yup4.object)().shape({
404
- description: (0, import_yup4.string)().optional().label("Description"),
405
- keywords: (0, import_yup4.array)().of((0, import_yup4.string)()).optional().label("Keywords")
578
+ seo: (0, import_yup5.object)().shape({
579
+ description: (0, import_yup5.string)().optional().label("Description"),
580
+ keywords: (0, import_yup5.array)().of((0, import_yup5.string)()).optional().label("Keywords")
406
581
  }).optional().label("SEO"),
407
- translation: (0, import_yup4.array)().of(GroupTranslationSchema).optional().label("Translations"),
408
- managedBy: (0, import_yup4.mixed)().oneOf(Object.values(GroupManagedBy)).default("community" /* Community */).label("Managed By"),
409
- status: (0, import_yup4.mixed)().oneOf(Object.values(GroupStatus)).default("active" /* Active */).label("Status"),
410
- createdAt: (0, import_yup4.number)().optional().label("Created At"),
411
- updatedAt: (0, import_yup4.number)().optional().label("Updated At")
582
+ translation: (0, import_yup5.array)().of(GroupTranslationSchema).optional().label("Translations"),
583
+ managedBy: (0, import_yup5.mixed)().oneOf(Object.values(GroupManagedBy)).default("community" /* Community */).label("Managed By"),
584
+ status: (0, import_yup5.mixed)().oneOf(Object.values(GroupStatus)).default("active" /* Active */).label("Status"),
585
+ createdAt: (0, import_yup5.number)().optional().label("Created At"),
586
+ updatedAt: (0, import_yup5.number)().optional().label("Updated At")
412
587
  }).label("Group Schema");
413
- var GroupMembershipSchema = (0, import_yup4.object)({
414
- groupId: (0, import_yup4.string)().required().label("Group ID"),
415
- userId: (0, import_yup4.string)().required().label("User ID"),
416
- status: (0, import_yup4.mixed)().oneOf(Object.values(GroupMembershipStatus)).required().label("Status"),
417
- createdAt: (0, import_yup4.number)().optional().label("Created At"),
418
- updatedAt: (0, import_yup4.number)().optional().label("Updated At")
588
+ var GroupMembershipSchema = (0, import_yup5.object)({
589
+ groupId: (0, import_yup5.string)().required().label("Group ID"),
590
+ userId: (0, import_yup5.string)().required().label("User ID"),
591
+ status: (0, import_yup5.mixed)().oneOf(Object.values(GroupMembershipStatus)).required().label("Status"),
592
+ createdAt: (0, import_yup5.number)().optional().label("Created At"),
593
+ updatedAt: (0, import_yup5.number)().optional().label("Updated At")
419
594
  });
420
595
 
596
+ // src/group/group-filter.schema.ts
597
+ var import_yup6 = require("yup");
598
+ var GroupFilterShape = (0, import_yup6.object)({
599
+ // ── Classification ──────────────────────────────────────────────────────────
600
+ // Closed vocabularies, matched exactly. Values are LOWERCASE (`active`,
601
+ // `public`, `system`) and that casing is data, not style: it is what is stored.
602
+ //
603
+ // All three are currently NON-SELECTIVE — every group in production is
604
+ // `active` / `public` / `system`. They are declared anyway because they are
605
+ // real vocabulary that gains meaning as the corpus grows, and a filter that
606
+ // silently ignores `status` would be worse than one that narrows to everything.
607
+ status: enumFacet(SupportedGroupStatuses, "Status"),
608
+ visibility: enumFacet(SupportedGroupVisibilities, "Visibility"),
609
+ managedBy: enumFacet(SupportedGroupManagedBy, "Managed By"),
610
+ // ── Flags ───────────────────────────────────────────────────────────────────
611
+ // No `.default(false)`: an unchecked box must mean "no constraint", not "only
612
+ // the non-featured ones", so absent simply omits the clause.
613
+ isFeatured: (0, import_yup6.boolean)().optional().label("Featured Only"),
614
+ isDiscoverable: (0, import_yup6.boolean)().optional().label("Discoverable Only"),
615
+ // ── Markets ─────────────────────────────────────────────────────────────────
616
+ /**
617
+ * `tokens`, NOT `codeTokens`. This is the trap in this file.
618
+ *
619
+ * Every other filter here uses `codeTokens` for country codes, because users
620
+ * and jobs store uppercase ISO alpha-2. Groups store them LOWERCASE (`in`,
621
+ * `se`, `ww` — verified against production). `codeTokens` uppercases its
622
+ * input, so `$in: ["IN"]` against a stored `"in"` matches nothing: no error,
623
+ * empty result, empty mail.
624
+ *
625
+ * If these are ever normalised to uppercase, this becomes `codeTokens` in the
626
+ * same change as the data migration, not before it.
627
+ */
628
+ countries: tokens("Countries"),
629
+ featuredMarkets: tokens("Featured Markets"),
630
+ /** Group SLUGS, for naming specific groups. A display name breaks on rename. */
631
+ slugs: tokens("Slugs")
632
+ }).label("Group Filter");
633
+ var GROUP_FILTER_KEYS = Object.keys(GroupFilterShape.fields);
634
+ var GroupFilterSchema = rejectUnknownKeys(
635
+ GroupFilterShape,
636
+ () => GROUP_FILTER_KEYS
637
+ );
638
+
421
639
  // src/job/job.schema.ts
422
- var import_yup18 = require("yup");
640
+ var import_yup20 = require("yup");
423
641
 
424
642
  // src/location/location.schema.ts
425
- var import_yup5 = require("yup");
426
- var LocationSchema = (0, import_yup5.object)({
427
- placeId: (0, import_yup5.string)().optional().nullable(),
428
- country: (0, import_yup5.string)().required(),
429
- countryCode: (0, import_yup5.string)().required(),
430
- state: (0, import_yup5.string)().optional().nullable(),
431
- stateCode: (0, import_yup5.string)().optional().nullable(),
432
- city: (0, import_yup5.string)().optional().nullable(),
433
- address: (0, import_yup5.string)().optional().nullable(),
434
- geo: (0, import_yup5.object)({
435
- type: (0, import_yup5.string)().oneOf(["Point"]).default("Point"),
436
- coordinates: (0, import_yup5.array)().of((0, import_yup5.number)().required()).length(2).required()
643
+ var import_yup7 = require("yup");
644
+ var LocationSchema = (0, import_yup7.object)({
645
+ placeId: (0, import_yup7.string)().optional().nullable(),
646
+ country: (0, import_yup7.string)().required(),
647
+ countryCode: (0, import_yup7.string)().required(),
648
+ state: (0, import_yup7.string)().optional().nullable(),
649
+ stateCode: (0, import_yup7.string)().optional().nullable(),
650
+ city: (0, import_yup7.string)().optional().nullable(),
651
+ address: (0, import_yup7.string)().optional().nullable(),
652
+ geo: (0, import_yup7.object)({
653
+ type: (0, import_yup7.string)().oneOf(["Point"]).default("Point"),
654
+ coordinates: (0, import_yup7.array)().of((0, import_yup7.number)().required()).length(2).required()
437
655
  // [longitude, latitude]
438
656
  }).required()
439
657
  });
440
658
 
441
659
  // src/page/page.schema.ts
442
- var import_yup12 = require("yup");
660
+ var import_yup14 = require("yup");
443
661
 
444
662
  // src/page/page.constant.ts
445
663
  var PageType = /* @__PURE__ */ ((PageType2) => {
@@ -477,44 +695,44 @@ var ContactTypes = /* @__PURE__ */ ((ContactTypes2) => {
477
695
  var SupportedContactTypes = Object.values(ContactTypes);
478
696
 
479
697
  // src/page/page-location.schema.ts
480
- var import_yup6 = require("yup");
481
- var PageLocationSchema = (0, import_yup6.object)().shape({
482
- isHeadquarters: (0, import_yup6.boolean)().oneOf([true, false]).default(false).label("Is Headquarters"),
483
- coverImage: (0, import_yup6.string)().optional().label("Cover Image"),
484
- name: (0, import_yup6.string)().required().label("Office Name"),
698
+ var import_yup8 = require("yup");
699
+ var PageLocationSchema = (0, import_yup8.object)().shape({
700
+ isHeadquarters: (0, import_yup8.boolean)().oneOf([true, false]).default(false).label("Is Headquarters"),
701
+ coverImage: (0, import_yup8.string)().optional().label("Cover Image"),
702
+ name: (0, import_yup8.string)().required().label("Office Name"),
485
703
  /* TODO: Add Location Schema to enable geo spacial search*/
486
704
  // location: LocationSchema,
487
- contact: (0, import_yup6.object)().shape({
488
- email: (0, import_yup6.string)().email().optional().label("Email"),
489
- phone: (0, import_yup6.string)().optional().label("Phone"),
490
- address: (0, import_yup6.string)().required().label("Address")
705
+ contact: (0, import_yup8.object)().shape({
706
+ email: (0, import_yup8.string)().email().optional().label("Email"),
707
+ phone: (0, import_yup8.string)().optional().label("Phone"),
708
+ address: (0, import_yup8.string)().required().label("Address")
491
709
  })
492
710
  }).label("PageLocationSchema");
493
711
 
494
712
  // src/page/institute/institute.schema.ts
495
- var import_yup7 = require("yup");
496
- var InstituteSchema = (0, import_yup7.object)().shape({}).label("InstituteSchema");
713
+ var import_yup9 = require("yup");
714
+ var InstituteSchema = (0, import_yup9.object)().shape({}).label("InstituteSchema");
497
715
 
498
716
  // src/page/company/company.schema.ts
499
- var import_yup10 = require("yup");
717
+ var import_yup12 = require("yup");
500
718
 
501
719
  // src/page/company/perks-and-benefits.schema.ts
502
- var import_yup8 = require("yup");
503
- var import_yup9 = require("yup");
504
- var PerksAndBenefitSchema = (0, import_yup9.object)().shape({
505
- name: (0, import_yup9.string)().trim().max(50).required().label("Name"),
506
- description: (0, import_yup9.string)().max(1e3).required().label("Description"),
507
- category: (0, import_yup8.array)().of((0, import_yup9.string)()).optional().label("Category")
720
+ var import_yup10 = require("yup");
721
+ var import_yup11 = require("yup");
722
+ var PerksAndBenefitSchema = (0, import_yup11.object)().shape({
723
+ name: (0, import_yup11.string)().trim().max(50).required().label("Name"),
724
+ description: (0, import_yup11.string)().max(1e3).required().label("Description"),
725
+ category: (0, import_yup10.array)().of((0, import_yup11.string)()).optional().label("Category")
508
726
  });
509
727
 
510
728
  // src/page/company/company.schema.ts
511
- var CompanySchema = (0, import_yup10.object)().shape({
512
- equalOpportunityEmployer: (0, import_yup10.boolean)().optional().label("Equal Opportunity Employer"),
513
- perksAndBenefits: (0, import_yup10.array)().of(PerksAndBenefitSchema).optional().label("Perks and Benefits")
729
+ var CompanySchema = (0, import_yup12.object)().shape({
730
+ equalOpportunityEmployer: (0, import_yup12.boolean)().optional().label("Equal Opportunity Employer"),
731
+ perksAndBenefits: (0, import_yup12.array)().of(PerksAndBenefitSchema).optional().label("Perks and Benefits")
514
732
  }).noUnknown().strict().label("Company Schema");
515
733
 
516
734
  // src/social-account/social-account.schema.ts
517
- var import_yup11 = require("yup");
735
+ var import_yup13 = require("yup");
518
736
 
519
737
  // src/social-account/social-account.constant.ts
520
738
  var SocialAccount = /* @__PURE__ */ ((SocialAccount2) => {
@@ -552,43 +770,43 @@ var SocialAccount = /* @__PURE__ */ ((SocialAccount2) => {
552
770
  var SupportedSocialAccounts = Object.values(SocialAccount);
553
771
 
554
772
  // src/social-account/social-account.schema.ts
555
- var SocialAccountSchema = (0, import_yup11.object)().shape({
773
+ var SocialAccountSchema = (0, import_yup13.object)().shape({
556
774
  // TODO: Remove isNew - it's unused. Backend (user-social-accounts.service.ts) just strips it before saving.
557
- isNew: (0, import_yup11.boolean)().default(false).optional().label("Is New"),
558
- type: (0, import_yup11.string)().oneOf(SupportedSocialAccounts).required().label("Account Type"),
559
- url: (0, import_yup11.string)().required().label("URL")
775
+ isNew: (0, import_yup13.boolean)().default(false).optional().label("Is New"),
776
+ type: (0, import_yup13.string)().oneOf(SupportedSocialAccounts).required().label("Account Type"),
777
+ url: (0, import_yup13.string)().required().label("URL")
560
778
  }).noUnknown().strict().label("Social Account");
561
779
 
562
780
  // src/page/page.schema.ts
563
- var PageSchema = (0, import_yup12.object)().shape({
564
- id: (0, import_yup12.string)().required().label("ID"),
565
- shortId: (0, import_yup12.string)().required().label("Short ID"),
566
- name: (0, import_yup12.string)().required().label("Name"),
567
- headline: (0, import_yup12.string)().max(100).optional().label("Headline"),
568
- about: (0, import_yup12.string)().required().min(100).max(1e4).label("About"),
569
- website: (0, import_yup12.string)().required().label("Website"),
570
- slug: (0, import_yup12.string)().required().label("Slug"),
571
- employeeCount: (0, import_yup12.string)().optional().label("Employee Count"),
572
- yearFounded: (0, import_yup12.string)().optional().label("Year Founded"),
573
- type: (0, import_yup12.string)().oneOf(SupportedPageTypes).required().label("Page Type"),
574
- locations: (0, import_yup12.array)().of(PageLocationSchema).optional().label("Locations"),
781
+ var PageSchema = (0, import_yup14.object)().shape({
782
+ id: (0, import_yup14.string)().required().label("ID"),
783
+ shortId: (0, import_yup14.string)().required().label("Short ID"),
784
+ name: (0, import_yup14.string)().required().label("Name"),
785
+ headline: (0, import_yup14.string)().max(100).optional().label("Headline"),
786
+ about: (0, import_yup14.string)().required().min(100).max(1e4).label("About"),
787
+ website: (0, import_yup14.string)().required().label("Website"),
788
+ slug: (0, import_yup14.string)().required().label("Slug"),
789
+ employeeCount: (0, import_yup14.string)().optional().label("Employee Count"),
790
+ yearFounded: (0, import_yup14.string)().optional().label("Year Founded"),
791
+ type: (0, import_yup14.string)().oneOf(SupportedPageTypes).required().label("Page Type"),
792
+ locations: (0, import_yup14.array)().of(PageLocationSchema).optional().label("Locations"),
575
793
  /*
576
794
  * @deprecated
577
795
  * 'locations' prop already has contact information, so this can be removed
578
796
  */
579
- contacts: (0, import_yup12.array)().of(
580
- (0, import_yup12.object)().shape({
581
- type: (0, import_yup12.string)().oneOf(SupportedContactTypes).required().label("Type"),
582
- value: (0, import_yup12.string)().required().label("Value"),
583
- label: (0, import_yup12.string)().optional().label("Label"),
584
- isPrimary: (0, import_yup12.boolean)().optional().default(false).label("Is Primary"),
585
- isVerified: (0, import_yup12.boolean)().optional().default(false).label("Is Verified")
797
+ contacts: (0, import_yup14.array)().of(
798
+ (0, import_yup14.object)().shape({
799
+ type: (0, import_yup14.string)().oneOf(SupportedContactTypes).required().label("Type"),
800
+ value: (0, import_yup14.string)().required().label("Value"),
801
+ label: (0, import_yup14.string)().optional().label("Label"),
802
+ isPrimary: (0, import_yup14.boolean)().optional().default(false).label("Is Primary"),
803
+ isVerified: (0, import_yup14.boolean)().optional().default(false).label("Is Verified")
586
804
  })
587
805
  ).optional().label("Contacts"),
588
- verified: (0, import_yup12.date)().optional().label("Verified"),
589
- logo: (0, import_yup12.object)().shape({
590
- dark: (0, import_yup12.string)().optional().nullable().label("Dark Logo"),
591
- light: (0, import_yup12.string)().required().label("Light Logo")
806
+ verified: (0, import_yup14.date)().optional().label("Verified"),
807
+ logo: (0, import_yup14.object)().shape({
808
+ dark: (0, import_yup14.string)().optional().nullable().label("Dark Logo"),
809
+ light: (0, import_yup14.string)().required().label("Light Logo")
592
810
  }).optional().nullable().default(null).label("Logo"),
593
811
  // Taxonomy (see job-taxonomy.constant.ts): category -> industry -> subCategory,
594
812
  // stored as slugs and translated for display. As on JobSchema, these are NOT
@@ -596,21 +814,21 @@ var PageSchema = (0, import_yup12.object)().shape({
596
814
  // never a reason to reject the whole page. `categories` is the top level (kept
597
815
  // required for backward compatibility); `industries` and `subCategories` are the
598
816
  // deeper levels the tree picker fills in when a leaf auto-selects its parents.
599
- categories: (0, import_yup12.array)().of((0, import_yup12.string)().trim().required()).required().min(1).label("Categories"),
600
- industries: (0, import_yup12.array)().of((0, import_yup12.string)().trim().required()).optional().default([]).label("Industries"),
601
- subCategories: (0, import_yup12.array)().of((0, import_yup12.string)().trim().required()).optional().default([]).label("Sub Categories"),
602
- socialAccounts: (0, import_yup12.array)().of(SocialAccountSchema).optional().label("Social Accounts"),
603
- isOwner: (0, import_yup12.boolean)().optional().label("Is Owner"),
604
- isFeatured: (0, import_yup12.boolean)().optional().default(false).label("Is Featured"),
605
- featuredMarkets: (0, import_yup12.array)().of((0, import_yup12.string)().required()).optional().default([]).label("Featured Markets"),
606
- status: (0, import_yup12.string)().oneOf(SupportedPageStatuses).required().label("Status"),
607
- followersCount: (0, import_yup12.number)().min(0).optional().label("Followers Count")
817
+ categories: (0, import_yup14.array)().of((0, import_yup14.string)().trim().required()).required().min(1).label("Categories"),
818
+ industries: (0, import_yup14.array)().of((0, import_yup14.string)().trim().required()).optional().default([]).label("Industries"),
819
+ subCategories: (0, import_yup14.array)().of((0, import_yup14.string)().trim().required()).optional().default([]).label("Sub Categories"),
820
+ socialAccounts: (0, import_yup14.array)().of(SocialAccountSchema).optional().label("Social Accounts"),
821
+ isOwner: (0, import_yup14.boolean)().optional().label("Is Owner"),
822
+ isFeatured: (0, import_yup14.boolean)().optional().default(false).label("Is Featured"),
823
+ featuredMarkets: (0, import_yup14.array)().of((0, import_yup14.string)().required()).optional().default([]).label("Featured Markets"),
824
+ status: (0, import_yup14.string)().oneOf(SupportedPageStatuses).required().label("Status"),
825
+ followersCount: (0, import_yup14.number)().min(0).optional().label("Followers Count")
608
826
  }).concat(CompanySchema).concat(InstituteSchema).concat(DbDefaultSchema).noUnknown(true).strict(true).required().label("Page Schema");
609
827
 
610
828
  // src/skill/skill.schema.ts
611
- var import_yup13 = require("yup");
612
- var SkillSchema = (0, import_yup13.object)({
613
- name: (0, import_yup13.string)().trim().required().label("Skill Name"),
829
+ var import_yup15 = require("yup");
830
+ var SkillSchema = (0, import_yup15.object)({
831
+ name: (0, import_yup15.string)().trim().required().label("Skill Name"),
614
832
  /**
615
833
  * The taxonomy concept this skill resolved to, assigned by thejob-taxonomy-service
616
834
  * via `POST /normalize/batch`. Stable slug, e.g. "power-bi".
@@ -628,14 +846,14 @@ var SkillSchema = (0, import_yup13.object)({
628
846
  *
629
847
  * Consumers must therefore read `conceptName ?? name`, never `conceptName` alone.
630
848
  */
631
- conceptId: (0, import_yup13.string)().optional().nullable().label("Concept ID"),
849
+ conceptId: (0, import_yup15.string)().optional().nullable().label("Concept ID"),
632
850
  /** Canonical display name for `conceptId`, e.g. "Power BI". See `conceptId`. */
633
- conceptName: (0, import_yup13.string)().optional().nullable().label("Concept Name"),
634
- logo: (0, import_yup13.object)({
635
- light: (0, import_yup13.string)().required().label("Light Logo"),
636
- dark: (0, import_yup13.string)().optional().nullable().label("Dark Logo")
851
+ conceptName: (0, import_yup15.string)().optional().nullable().label("Concept Name"),
852
+ logo: (0, import_yup15.object)({
853
+ light: (0, import_yup15.string)().required().label("Light Logo"),
854
+ dark: (0, import_yup15.string)().optional().nullable().label("Dark Logo")
637
855
  }).optional().nullable().default(null).label("Logo"),
638
- tags: (0, import_yup13.array)().of((0, import_yup13.string)().max(50)).max(20).optional().label("Tags")
856
+ tags: (0, import_yup15.array)().of((0, import_yup15.string)().max(50)).max(20).optional().label("Tags")
639
857
  }).concat(DbDefaultSchema).noUnknown().strict().label("Skill");
640
858
 
641
859
  // src/question/question.constant.ts
@@ -654,24 +872,24 @@ var AnswerChoiceType = /* @__PURE__ */ ((AnswerChoiceType2) => {
654
872
  var SupportedAnswerChoiceTypes = Object.values(AnswerChoiceType);
655
873
 
656
874
  // src/question/question.schema.ts
657
- var import_yup14 = require("yup");
658
- var QuestionSchema = (0, import_yup14.object)().shape({
659
- label: (0, import_yup14.string)().max(50).optional().label("Label"),
660
- default: (0, import_yup14.boolean)().optional().default(false).label("Default"),
661
- title: (0, import_yup14.string)().required().max(200).label("Title"),
662
- type: (0, import_yup14.string)().oneOf(SupportedQuestionTypes).required().label("Type"),
663
- isOptional: (0, import_yup14.boolean)().optional().label("Mark if the question is optional"),
664
- readonly: (0, import_yup14.boolean)().optional().label("Readonly"),
665
- isNew: (0, import_yup14.boolean)().optional().label("Is New")
875
+ var import_yup16 = require("yup");
876
+ var QuestionSchema = (0, import_yup16.object)().shape({
877
+ label: (0, import_yup16.string)().max(50).optional().label("Label"),
878
+ default: (0, import_yup16.boolean)().optional().default(false).label("Default"),
879
+ title: (0, import_yup16.string)().required().max(200).label("Title"),
880
+ type: (0, import_yup16.string)().oneOf(SupportedQuestionTypes).required().label("Type"),
881
+ isOptional: (0, import_yup16.boolean)().optional().label("Mark if the question is optional"),
882
+ readonly: (0, import_yup16.boolean)().optional().label("Readonly"),
883
+ isNew: (0, import_yup16.boolean)().optional().label("Is New")
666
884
  });
667
885
 
668
886
  // src/question/input-question.schema.ts
669
- var import_yup15 = require("yup");
887
+ var import_yup17 = require("yup");
670
888
  var InputQuestionSchema = QuestionSchema.concat(
671
- (0, import_yup15.object)().shape({
672
- type: (0, import_yup15.string)().oneOf(["input" /* Input */]).required().label("Question type"),
673
- preferredAnswer: (0, import_yup15.string)().max(100).optional().label("Preferred Answer"),
674
- answers: (0, import_yup15.string)().max(500).when("isOptional", {
889
+ (0, import_yup17.object)().shape({
890
+ type: (0, import_yup17.string)().oneOf(["input" /* Input */]).required().label("Question type"),
891
+ preferredAnswer: (0, import_yup17.string)().max(100).optional().label("Preferred Answer"),
892
+ answers: (0, import_yup17.string)().max(500).when("isOptional", {
675
893
  is: (isOptional) => Boolean(isOptional),
676
894
  then: (schema) => schema.optional(),
677
895
  otherwise: (schema) => schema.required()
@@ -680,10 +898,10 @@ var InputQuestionSchema = QuestionSchema.concat(
680
898
  );
681
899
 
682
900
  // src/question/choice-question.schema.ts
683
- var import_yup16 = require("yup");
684
- var ChoicePreferredAnswerSchema = (label = "Preferred answer") => (0, import_yup16.mixed)().when("answerChoiceType", {
901
+ var import_yup18 = require("yup");
902
+ var ChoicePreferredAnswerSchema = (label = "Preferred answer") => (0, import_yup18.mixed)().when("answerChoiceType", {
685
903
  is: (value) => value === "multiple" /* Multiple */,
686
- then: () => (0, import_yup16.array)((0, import_yup16.string)()).test(
904
+ then: () => (0, import_yup18.array)((0, import_yup18.string)()).test(
687
905
  "is-valid-option",
688
906
  `${label} must be from the provided options`,
689
907
  function(answer) {
@@ -694,7 +912,7 @@ var ChoicePreferredAnswerSchema = (label = "Preferred answer") => (0, import_yup
694
912
  return true;
695
913
  }
696
914
  ).optional().label(label),
697
- otherwise: () => (0, import_yup16.string)().test(
915
+ otherwise: () => (0, import_yup18.string)().test(
698
916
  "is-valid-option",
699
917
  `${label} must be from the provided options`,
700
918
  function(answer) {
@@ -705,20 +923,20 @@ var ChoicePreferredAnswerSchema = (label = "Preferred answer") => (0, import_yup
705
923
  });
706
924
  var getChoiceAnswerSchema = (answerChoiceType, isOptional) => {
707
925
  if (answerChoiceType === "multiple" /* Multiple */) {
708
- const multiAnswerSchema = (0, import_yup16.array)((0, import_yup16.string)().max(500));
926
+ const multiAnswerSchema = (0, import_yup18.array)((0, import_yup18.string)().max(500));
709
927
  return isOptional ? multiAnswerSchema.optional().default([]) : multiAnswerSchema.min(1, "Select at least one option.").required("Select at least one option.").default([]);
710
928
  }
711
- const singleAnswerSchema = (0, import_yup16.string)().max(500);
929
+ const singleAnswerSchema = (0, import_yup18.string)().max(500);
712
930
  return isOptional ? singleAnswerSchema.optional() : singleAnswerSchema.required("Select an option.");
713
931
  };
714
932
  var ChoiceQuestionSchema = QuestionSchema.concat(
715
- (0, import_yup16.object)().shape({
716
- type: (0, import_yup16.string)().oneOf(["choice" /* Choice */]).required().label("Question type"),
717
- optionsFrom: (0, import_yup16.string)().oneOf(["skill" /* Skill */, "education_level" /* EducationLevel */, "experience_level" /* ExperienceLevel */]).optional().label("Option field"),
718
- options: (0, import_yup16.array)((0, import_yup16.string)().required()).min(1).required().label("Options"),
719
- answerChoiceType: (0, import_yup16.string)().oneOf(SupportedAnswerChoiceTypes).required().default("single" /* Single */).label("Type"),
933
+ (0, import_yup18.object)().shape({
934
+ type: (0, import_yup18.string)().oneOf(["choice" /* Choice */]).required().label("Question type"),
935
+ optionsFrom: (0, import_yup18.string)().oneOf(["skill" /* Skill */, "education_level" /* EducationLevel */, "experience_level" /* ExperienceLevel */]).optional().label("Option field"),
936
+ options: (0, import_yup18.array)((0, import_yup18.string)().required()).min(1).required().label("Options"),
937
+ answerChoiceType: (0, import_yup18.string)().oneOf(SupportedAnswerChoiceTypes).required().default("single" /* Single */).label("Type"),
720
938
  preferredAnswer: ChoicePreferredAnswerSchema("Preferred answer"),
721
- answers: (0, import_yup16.mixed)().when(
939
+ answers: (0, import_yup18.mixed)().when(
722
940
  ["answerChoiceType", "isOptional"],
723
941
  ([answerChoiceType, isOptional]) => getChoiceAnswerSchema(answerChoiceType, isOptional)
724
942
  )
@@ -726,12 +944,12 @@ var ChoiceQuestionSchema = QuestionSchema.concat(
726
944
  );
727
945
 
728
946
  // src/question/read-and-acknowledge.schema.ts
729
- var import_yup17 = require("yup");
730
- var AcknowledgeAnswerSchema = (0, import_yup17.string)().oneOf(["yes" /* Yes */, "no" /* No */]).required("This question must be acknowledged.");
947
+ var import_yup19 = require("yup");
948
+ var AcknowledgeAnswerSchema = (0, import_yup19.string)().oneOf(["yes" /* Yes */, "no" /* No */]).required("This question must be acknowledged.");
731
949
  var ReadAndAcknowledgeQuestionSchema = QuestionSchema.concat(
732
- (0, import_yup17.object)().shape({
733
- type: (0, import_yup17.string)().oneOf(["readAndAcknowledge" /* ReadAndAcknowledge */]).required().label("Question type"),
734
- description: (0, import_yup17.string)().max(1e4).required().label("Content"),
950
+ (0, import_yup19.object)().shape({
951
+ type: (0, import_yup19.string)().oneOf(["readAndAcknowledge" /* ReadAndAcknowledge */]).required().label("Question type"),
952
+ description: (0, import_yup19.string)().max(1e4).required().label("Content"),
735
953
  preferredAnswer: AcknowledgeAnswerSchema.label("Preferred Answer"),
736
954
  answers: AcknowledgeAnswerSchema.label("Answer")
737
955
  })
@@ -812,20 +1030,20 @@ var SupportedApplicationReceivePreferences = Object.values(
812
1030
  );
813
1031
 
814
1032
  // src/job/job.schema.ts
815
- var JobSchema = (0, import_yup18.object)({
1033
+ var JobSchema = (0, import_yup20.object)({
816
1034
  /** Unique id usable by external systems to reference the job. */
817
- uniqueId: (0, import_yup18.string)().optional().label("Unique ID"),
1035
+ uniqueId: (0, import_yup20.string)().optional().label("Unique ID"),
818
1036
  /** Job title shown in listings and search. */
819
- headline: (0, import_yup18.string)().required().min(5).max(150).label("Headline"),
1037
+ headline: (0, import_yup20.string)().required().min(5).max(150).label("Headline"),
820
1038
  /** User-friendly, unique URL identifier. */
821
- slug: (0, import_yup18.string)().required().max(250).label("Slug"),
1039
+ slug: (0, import_yup20.string)().required().max(250).label("Slug"),
822
1040
  /** How applications are received (inbox vs external website). */
823
- applicationReceivePreference: (0, import_yup18.mixed)().oneOf([
1041
+ applicationReceivePreference: (0, import_yup20.mixed)().oneOf([
824
1042
  "inbox" /* Inbox */,
825
1043
  "external_website" /* ExternalWebsite */
826
1044
  ]).required().label("Preference"),
827
1045
  /** Required when applicationReceivePreference is ExternalWebsite. */
828
- externalApplyLink: (0, import_yup18.string)().when("applicationReceivePreference", {
1046
+ externalApplyLink: (0, import_yup20.string)().when("applicationReceivePreference", {
829
1047
  is: (value) => value === "external_website" /* ExternalWebsite */,
830
1048
  then: (schema) => schema.url().required().label("Apply for Job link"),
831
1049
  otherwise: (schema) => schema.optional()
@@ -833,35 +1051,35 @@ var JobSchema = (0, import_yup18.object)({
833
1051
  /** Company the job belongs to (subset of PageSchema). */
834
1052
  company: PageSchema.pick(["name", "slug", "type", "logo"]).deepPartial().concat(PageSchema.pick(["name", "type"])).required().label("Company"),
835
1053
  /** @deprecated kept for backwards compatibility. */
836
- designation: (0, import_yup18.string)().optional().nullable().label("Designation"),
837
- employmentType: (0, import_yup18.string)().required().label("Employment type"),
838
- workMode: (0, import_yup18.string)().required().label("Work mode"),
1054
+ designation: (0, import_yup20.string)().optional().nullable().label("Designation"),
1055
+ employmentType: (0, import_yup20.string)().required().label("Employment type"),
1056
+ workMode: (0, import_yup20.string)().required().label("Work mode"),
839
1057
  /**
840
1058
  * `conceptId`/`conceptName` carry the taxonomy-service normalization. They are
841
1059
  * picked here (not just `name`/`logo`) because SkillSchema is `.noUnknown()`:
842
1060
  * an unpicked field that is actually PRESENT fails validation, so omitting them
843
1061
  * would reject every normalized job.
844
1062
  */
845
- skills: (0, import_yup18.array)().of(
1063
+ skills: (0, import_yup20.array)().of(
846
1064
  SkillSchema.pick(["name", "logo", "conceptId", "conceptName"]).required().label("Skill")
847
1065
  ).nullable().optional().label("Skills"),
848
- bookmarks: (0, import_yup18.array)().of(UserIdAndCreatedAtSchema).optional().label("Bookmarks"),
849
- embedding: (0, import_yup18.object)({
850
- vector: (0, import_yup18.array)((0, import_yup18.number)().required()).optional().label("Embedding vector"),
851
- model: (0, import_yup18.string)().optional().label("Embedding model")
1066
+ bookmarks: (0, import_yup20.array)().of(UserIdAndCreatedAtSchema).optional().label("Bookmarks"),
1067
+ embedding: (0, import_yup20.object)({
1068
+ vector: (0, import_yup20.array)((0, import_yup20.number)().required()).optional().label("Embedding vector"),
1069
+ model: (0, import_yup20.string)().optional().label("Embedding model")
852
1070
  }).nullable().optional().label("Embedding"),
853
- applicants: (0, import_yup18.array)().of(UserIdAndCreatedAtSchema).optional().label("Applicants"),
854
- applicantCount: (0, import_yup18.number)().optional().label("Applicant count"),
855
- externalApplicationLinkClickCount: (0, import_yup18.number)().optional().label("External application link click count"),
856
- description: (0, import_yup18.string)().required().min(100).max(5e4).label("Description"),
857
- descriptionMarkdown: (0, import_yup18.string)().required().label("Description Markdown"),
858
- descriptionHTML: (0, import_yup18.string)().optional().label("Description HTML"),
1071
+ applicants: (0, import_yup20.array)().of(UserIdAndCreatedAtSchema).optional().label("Applicants"),
1072
+ applicantCount: (0, import_yup20.number)().optional().label("Applicant count"),
1073
+ externalApplicationLinkClickCount: (0, import_yup20.number)().optional().label("External application link click count"),
1074
+ description: (0, import_yup20.string)().required().min(100).max(5e4).label("Description"),
1075
+ descriptionMarkdown: (0, import_yup20.string)().required().label("Description Markdown"),
1076
+ descriptionHTML: (0, import_yup20.string)().optional().label("Description HTML"),
859
1077
  /**
860
1078
  * Job locations. Required even for remote roles (compliance/legal). Defaults
861
1079
  * to an empty array; min(1) is intentionally not enforced yet.
862
1080
  */
863
- locations: (0, import_yup18.array)().of(LocationSchema).default([]).label("Location"),
864
- educationLevel: (0, import_yup18.array)((0, import_yup18.string)().oneOf(SupportedEducationLevels)).required().min(1).label("Educational level"),
1081
+ locations: (0, import_yup20.array)().of(LocationSchema).default([]).label("Location"),
1082
+ educationLevel: (0, import_yup20.array)((0, import_yup20.string)().oneOf(SupportedEducationLevels)).required().min(1).label("Educational level"),
865
1083
  /** Posting validity window (start/expiry). */
866
1084
  validity: DurationSchema({
867
1085
  format: SYSTEM_DATE_FORMAT,
@@ -874,22 +1092,22 @@ var JobSchema = (0, import_yup18.object)({
874
1092
  * has no `validity.endDate` and must be verified by crawling its page. Guards
875
1093
  * re-check cadence so the same job is not re-crawled on every sweep.
876
1094
  */
877
- lastExpiryCheckAt: (0, import_yup18.number)().optional().nullable().label("Last expiry check at"),
1095
+ lastExpiryCheckAt: (0, import_yup20.number)().optional().nullable().label("Last expiry check at"),
878
1096
  /**
879
1097
  * When this job transitioned to `status: closed` (epoch ms). Server-owned
880
1098
  * operational field: stamped by every jobs-service close path (expiry
881
1099
  * auto-close and the dated lapse sweep). Distinct from `updatedAt` so a later
882
1100
  * edit to a closed job does not move it.
883
1101
  */
884
- closedAt: (0, import_yup18.number)().optional().nullable().label("Closed at"),
885
- experienceLevel: (0, import_yup18.string)().oneOf(SupportedExperienceLevels).nullable().optional().label("Experience level"),
886
- contactEmail: (0, import_yup18.string)().email().nullable().optional().label("Contact email"),
887
- workingHoursPerWeek: (0, import_yup18.number)().nullable().optional().min(1).max(24 * 7).label("Working hours per week"),
888
- tags: (0, import_yup18.array)().max(50).optional().label("Tags"),
1102
+ closedAt: (0, import_yup20.number)().optional().nullable().label("Closed at"),
1103
+ experienceLevel: (0, import_yup20.string)().oneOf(SupportedExperienceLevels).nullable().optional().label("Experience level"),
1104
+ contactEmail: (0, import_yup20.string)().email().nullable().optional().label("Contact email"),
1105
+ workingHoursPerWeek: (0, import_yup20.number)().nullable().optional().min(1).max(24 * 7).label("Working hours per week"),
1106
+ tags: (0, import_yup20.array)().max(50).optional().label("Tags"),
889
1107
  /** Direct/Quick-apply questionnaire. Each item is validated against the
890
1108
  * concrete question schema for its `type`, with the stored `answers` omitted. */
891
- questionnaire: (0, import_yup18.array)().of(
892
- (0, import_yup18.lazy)((question) => {
1109
+ questionnaire: (0, import_yup20.array)().of(
1110
+ (0, import_yup20.lazy)((question) => {
893
1111
  const schema = getSchemaByQuestion(question.type);
894
1112
  if (question.type === "input" /* Input */) {
895
1113
  return schema.omit([
@@ -905,12 +1123,12 @@ var JobSchema = (0, import_yup18.object)({
905
1123
  return schema;
906
1124
  })
907
1125
  ).label("Questionnaire"),
908
- status: (0, import_yup18.string)().oneOf(SupportedJobStatuses).default("draft" /* Draft */).required().label("Status"),
909
- seoTags: (0, import_yup18.object)({
910
- description: (0, import_yup18.string)().optional().label("Description"),
911
- keywords: (0, import_yup18.array)().of((0, import_yup18.string)()).optional().label("Keywords")
1126
+ status: (0, import_yup20.string)().oneOf(SupportedJobStatuses).default("draft" /* Draft */).required().label("Status"),
1127
+ seoTags: (0, import_yup20.object)({
1128
+ description: (0, import_yup20.string)().optional().label("Description"),
1129
+ keywords: (0, import_yup20.array)().of((0, import_yup20.string)()).optional().label("Keywords")
912
1130
  }).nullable().optional().label("SEO Tags"),
913
- jdSummary: (0, import_yup18.string)().optional().label("JD Summary"),
1131
+ jdSummary: (0, import_yup20.string)().optional().label("JD Summary"),
914
1132
  /**
915
1133
  * Structured, model-extracted sections of the posting, re-organized into clean
916
1134
  * bullet points. This is our OWN unique presentation of the job (rendered as the
@@ -919,28 +1137,28 @@ var JobSchema = (0, import_yup18.object)({
919
1137
  * optional: absent on jobs not yet (re-)enriched, and any section is `[]`/omitted
920
1138
  * when the posting genuinely has none.
921
1139
  */
922
- jobHighlights: (0, import_yup18.object)({
923
- responsibilities: (0, import_yup18.array)().of((0, import_yup18.string)()).optional().label("Responsibilities"),
924
- requirements: (0, import_yup18.array)().of((0, import_yup18.string)()).optional().label("Requirements"),
925
- benefits: (0, import_yup18.array)().of((0, import_yup18.string)()).optional().label("Benefits"),
926
- qualifications: (0, import_yup18.array)().of((0, import_yup18.string)()).optional().label("Qualifications")
1140
+ jobHighlights: (0, import_yup20.object)({
1141
+ responsibilities: (0, import_yup20.array)().of((0, import_yup20.string)()).optional().label("Responsibilities"),
1142
+ requirements: (0, import_yup20.array)().of((0, import_yup20.string)()).optional().label("Requirements"),
1143
+ benefits: (0, import_yup20.array)().of((0, import_yup20.string)()).optional().label("Benefits"),
1144
+ qualifications: (0, import_yup20.array)().of((0, import_yup20.string)()).optional().label("Qualifications")
927
1145
  }).nullable().optional().label("Job Highlights"),
928
- canCreateAlert: (0, import_yup18.boolean)().optional().label("Can create alert"),
929
- canDirectApply: (0, import_yup18.boolean)().optional().label("Can direct apply"),
930
- hasApplied: (0, import_yup18.boolean)().optional().label("Has applied"),
931
- isOwner: (0, import_yup18.boolean)().optional().label("Is owner"),
932
- hasBookmarked: (0, import_yup18.boolean)().optional().label("Has bookmarked"),
933
- hasReported: (0, import_yup18.boolean)().optional().label("Has reported"),
934
- ratePerHour: (0, import_yup18.number)().optional().min(0).label("Rate per hour"),
935
- isPromoted: (0, import_yup18.boolean)().optional().label("Is promoted"),
936
- salaryRange: (0, import_yup18.object)({
937
- currency: (0, import_yup18.string)().required().label("Currency").default("USD"),
938
- min: (0, import_yup18.number)().nullable().optional().min(0).label("Minimum Salary"),
939
- max: (0, import_yup18.number)().nullable().optional().min(0).label("Maximum Salary"),
940
- compensationType: (0, import_yup18.string)().oneOf(SupportedCompensationTypes).nullable().optional().label("Compensation Type"),
941
- isNegotiable: (0, import_yup18.boolean)().nullable().optional().default(true).label("Is Negotiable")
1146
+ canCreateAlert: (0, import_yup20.boolean)().optional().label("Can create alert"),
1147
+ canDirectApply: (0, import_yup20.boolean)().optional().label("Can direct apply"),
1148
+ hasApplied: (0, import_yup20.boolean)().optional().label("Has applied"),
1149
+ isOwner: (0, import_yup20.boolean)().optional().label("Is owner"),
1150
+ hasBookmarked: (0, import_yup20.boolean)().optional().label("Has bookmarked"),
1151
+ hasReported: (0, import_yup20.boolean)().optional().label("Has reported"),
1152
+ ratePerHour: (0, import_yup20.number)().optional().min(0).label("Rate per hour"),
1153
+ isPromoted: (0, import_yup20.boolean)().optional().label("Is promoted"),
1154
+ salaryRange: (0, import_yup20.object)({
1155
+ currency: (0, import_yup20.string)().required().label("Currency").default("USD"),
1156
+ min: (0, import_yup20.number)().nullable().optional().min(0).label("Minimum Salary"),
1157
+ max: (0, import_yup20.number)().nullable().optional().min(0).label("Maximum Salary"),
1158
+ compensationType: (0, import_yup20.string)().oneOf(SupportedCompensationTypes).nullable().optional().label("Compensation Type"),
1159
+ isNegotiable: (0, import_yup20.boolean)().nullable().optional().default(true).label("Is Negotiable")
942
1160
  }).nullable().optional().label("Salary Range"),
943
- reports: (0, import_yup18.array)().of(UserIdAndCreatedAtSchema).optional().label("Reports"),
1161
+ reports: (0, import_yup20.array)().of(UserIdAndCreatedAtSchema).optional().label("Reports"),
944
1162
  // Taxonomy fields are constants (see job-taxonomy.constant.ts), stored as slugs and
945
1163
  // translated for display. They are deliberately NOT `oneOf`-constrained here: a value
946
1164
  // outside the taxonomy must be DROPPED, never a reason to reject the posting. Yup
@@ -953,15 +1171,135 @@ var JobSchema = (0, import_yup18.object)({
953
1171
  // time (values get renamed, retired, added), so stored jobs will always outlive the
954
1172
  // enum — being lenient here is what lets the taxonomy evolve without a migration
955
1173
  // gate on every write.
956
- category: (0, import_yup18.string)().nullable().optional().label("Category"),
957
- industry: (0, import_yup18.string)().nullable().optional().label("Industry"),
958
- subCategories: (0, import_yup18.array)().of((0, import_yup18.string)().trim().required()).nullable().optional().label("Sub Categories"),
959
- termsAccepted: (0, import_yup18.boolean)().oneOf([true], "Accept terms before proceeding").required("Accept terms before proceeding").label("Terms accepted"),
960
- isFeatured: (0, import_yup18.boolean)().optional().default(false).label("Is featured"),
1174
+ category: (0, import_yup20.string)().nullable().optional().label("Category"),
1175
+ industry: (0, import_yup20.string)().nullable().optional().label("Industry"),
1176
+ subCategories: (0, import_yup20.array)().of((0, import_yup20.string)().trim().required()).nullable().optional().label("Sub Categories"),
1177
+ termsAccepted: (0, import_yup20.boolean)().oneOf([true], "Accept terms before proceeding").required("Accept terms before proceeding").label("Terms accepted"),
1178
+ isFeatured: (0, import_yup20.boolean)().optional().default(false).label("Is featured"),
961
1179
  /** Market country codes to feature this job in; empty = global. */
962
- featuredMarkets: (0, import_yup18.array)().of((0, import_yup18.string)().required()).optional().default([]).label("Featured markets")
1180
+ featuredMarkets: (0, import_yup20.array)().of((0, import_yup20.string)().required()).optional().default([]).label("Featured markets")
963
1181
  }).concat(DbDefaultSchema).noUnknown().label("Job");
964
1182
 
1183
+ // src/job/job-filter.schema.ts
1184
+ var import_yup21 = require("yup");
1185
+
1186
+ // src/user/user.constant.ts
1187
+ var UserRole = /* @__PURE__ */ ((UserRole2) => {
1188
+ UserRole2["Admin"] = "admin";
1189
+ UserRole2["Moderator"] = "moderator";
1190
+ UserRole2["Manager"] = "manager";
1191
+ UserRole2["Recruiter"] = "recruiter";
1192
+ UserRole2["JobSeeker"] = "job_seeker";
1193
+ UserRole2["Coordinator"] = "coordinator";
1194
+ UserRole2["System"] = "system";
1195
+ UserRole2["Normal"] = "normal";
1196
+ return UserRole2;
1197
+ })(UserRole || {});
1198
+ var SupportedUserRoles = Object.values(UserRole);
1199
+ var UserStatus = /* @__PURE__ */ ((UserStatus2) => {
1200
+ UserStatus2["Active"] = "active";
1201
+ UserStatus2["Blocked"] = "blocked";
1202
+ UserStatus2["Pending"] = "pending";
1203
+ UserStatus2["Deleted"] = "deleted";
1204
+ UserStatus2["MarkedForDeletion"] = "marked_for_deletion";
1205
+ return UserStatus2;
1206
+ })(UserStatus || {});
1207
+ var SupportedUserStatuses = Object.values(UserStatus);
1208
+ var DefaultUserRoles = ["normal" /* Normal */];
1209
+ var UserProfileVisibility = /* @__PURE__ */ ((UserProfileVisibility2) => {
1210
+ UserProfileVisibility2["Public"] = "public";
1211
+ UserProfileVisibility2["Private"] = "private";
1212
+ UserProfileVisibility2["Hidden"] = "hidden";
1213
+ return UserProfileVisibility2;
1214
+ })(UserProfileVisibility || {});
1215
+ var SupportedUserProfileVisibilities = Object.values(
1216
+ UserProfileVisibility
1217
+ );
1218
+ var SignupContextSource = /* @__PURE__ */ ((SignupContextSource2) => {
1219
+ SignupContextSource2["CfHeader"] = "cf-header";
1220
+ SignupContextSource2["Location"] = "location";
1221
+ SignupContextSource2["Default"] = "default";
1222
+ return SignupContextSource2;
1223
+ })(SignupContextSource || {});
1224
+ var SupportedSignupContextSources = Object.values(SignupContextSource);
1225
+ var UserDetailType = /* @__PURE__ */ ((UserDetailType2) => {
1226
+ UserDetailType2["Overview"] = "overview";
1227
+ UserDetailType2["AdditionalInfo"] = "additionalInfo";
1228
+ UserDetailType2["General"] = "general";
1229
+ UserDetailType2["SocialAccounts"] = "socialAccounts";
1230
+ UserDetailType2["WorkExperiences"] = "workExperiences";
1231
+ UserDetailType2["Educations"] = "educations";
1232
+ UserDetailType2["Skills"] = "skills";
1233
+ UserDetailType2["Languages"] = "languages";
1234
+ UserDetailType2["Certifications"] = "certifications";
1235
+ UserDetailType2["Interests"] = "interests";
1236
+ UserDetailType2["Projects"] = "projects";
1237
+ return UserDetailType2;
1238
+ })(UserDetailType || {});
1239
+ var ProficiencyLevel = /* @__PURE__ */ ((ProficiencyLevel2) => {
1240
+ ProficiencyLevel2["BasicAwareness"] = "basic_awareness";
1241
+ ProficiencyLevel2["Beginner"] = "beginner";
1242
+ ProficiencyLevel2["Intermediate"] = "intermediate";
1243
+ ProficiencyLevel2["Advanced"] = "advanced";
1244
+ ProficiencyLevel2["Expert"] = "expert";
1245
+ return ProficiencyLevel2;
1246
+ })(ProficiencyLevel || {});
1247
+ var SupportedProficiencyLevels = Object.values(ProficiencyLevel);
1248
+ var JobSearchUrgency = /* @__PURE__ */ ((JobSearchUrgency2) => {
1249
+ JobSearchUrgency2["Asap"] = "asap";
1250
+ JobSearchUrgency2["WithinThreeMonths"] = "within_3_months";
1251
+ JobSearchUrgency2["WithinSixMonths"] = "within_6_months";
1252
+ JobSearchUrgency2["PassivelyBrowsing"] = "passively_browsing";
1253
+ return JobSearchUrgency2;
1254
+ })(JobSearchUrgency || {});
1255
+ var SupportedJobSearchUrgencies = Object.values(JobSearchUrgency);
1256
+ var ReferralSource = /* @__PURE__ */ ((ReferralSource2) => {
1257
+ ReferralSource2["Recruiter"] = "recruiter";
1258
+ ReferralSource2["LinkedIn"] = "linkedin";
1259
+ ReferralSource2["TikTokInstagram"] = "tiktok_instagram";
1260
+ ReferralSource2["Google"] = "google";
1261
+ ReferralSource2["Reddit"] = "reddit";
1262
+ ReferralSource2["ChatGptGemini"] = "chatgpt_gemini";
1263
+ ReferralSource2["Friend"] = "friend";
1264
+ ReferralSource2["Other"] = "other";
1265
+ return ReferralSource2;
1266
+ })(ReferralSource || {});
1267
+ var SupportedReferralSources = Object.values(ReferralSource);
1268
+ var JobAlertFrequency = /* @__PURE__ */ ((JobAlertFrequency2) => {
1269
+ JobAlertFrequency2["Off"] = "off";
1270
+ JobAlertFrequency2["Daily"] = "daily";
1271
+ JobAlertFrequency2["Weekly"] = "weekly";
1272
+ return JobAlertFrequency2;
1273
+ })(JobAlertFrequency || {});
1274
+ var SupportedJobAlertFrequencies = Object.values(JobAlertFrequency);
1275
+ var NotificationType = /* @__PURE__ */ ((NotificationType2) => {
1276
+ NotificationType2["GroupActivity"] = "groupActivity";
1277
+ NotificationType2["JobActivity"] = "jobActivity";
1278
+ NotificationType2["ApplicationUpdates"] = "applicationUpdates";
1279
+ NotificationType2["ProductUpdates"] = "productUpdates";
1280
+ return NotificationType2;
1281
+ })(NotificationType || {});
1282
+ var SupportedNotificationTypes = Object.values(NotificationType);
1283
+ var SupportedSalaryCurrencies = ["USD", "EUR", "GBP", "SEK", "INR"];
1284
+ var MIN_SALARY_LOWER_BOUND = 0;
1285
+ var MIN_SALARY_UPPER_BOUND = 1e6;
1286
+ var PREFILL_MIRRORED_KEYS = [
1287
+ "headline",
1288
+ "aboutMe",
1289
+ "mobile",
1290
+ "location",
1291
+ "experienceLevel",
1292
+ "socialAccounts",
1293
+ "workExperiences",
1294
+ "educations"
1295
+ ];
1296
+ var PRIVATE_PROFILE_FIELDS = [
1297
+ "email",
1298
+ "emailVerified",
1299
+ "mobile",
1300
+ "additionalInfo"
1301
+ ];
1302
+
965
1303
  // src/job/job-taxonomy.constant.ts
966
1304
  var JobCategory = /* @__PURE__ */ ((JobCategory2) => {
967
1305
  JobCategory2["TechnologyDataAndDigital"] = "technology_data_and_digital";
@@ -2029,6 +2367,185 @@ var TAXONOMY_LABELS = {
2029
2367
  "fleet_management": "Fleet Management"
2030
2368
  };
2031
2369
 
2370
+ // src/job/job-filter.schema.ts
2371
+ var GEO_GROUP = "Geo";
2372
+ var JobFilterShape = (0, import_yup21.object)({
2373
+ // ── Classification ──────────────────────────────────────────────────────────
2374
+ // Closed vocabularies, sent verbatim and matched exactly: a partial match
2375
+ // would make "remote" also match a hypothetical "remote_first".
2376
+ employmentType: enumFacet(SupportedEmploymentTypes, "Employment Type"),
2377
+ workMode: enumFacet(SupportedWorkModes, "Work Mode"),
2378
+ experienceLevel: enumFacet(SupportedExperienceLevels, "Experience Level"),
2379
+ educationLevel: enumFacet(SupportedEducationLevels, "Education Level"),
2380
+ status: enumFacet(SupportedJobStatuses, "Status"),
2381
+ // ── Taxonomy ────────────────────────────────────────────────────────────────
2382
+ // NOT `enumFacet`, deliberately. Taxonomy is stored as the ENGLISH LABEL
2383
+ // ("Technology, Data & Digital"), not the enum constant, so a caller sends the
2384
+ // label and constraining these to `SupportedJobCategories` would reject every
2385
+ // value that actually matches.
2386
+ //
2387
+ // `meta.options` publishes the vocabulary anyway, for forms only. The schema
2388
+ // still accepts any string — it must, per the note above — but an admin
2389
+ // picking one of eight categories should not be typing it: the stored label
2390
+ // is "Technology, Data & Digital", and a hand-typed "Technology" matches
2391
+ // nothing while looking correctly configured.
2392
+ category: tokens("Category").meta({ options: SupportedJobCategories }),
2393
+ industry: tokens("Industry").meta({ options: SupportedJobIndustries }),
2394
+ subCategories: tokens("Sub-category").meta({
2395
+ options: SupportedJobSubCategories
2396
+ }),
2397
+ tags: tokens("Tags"),
2398
+ // ── Identity ────────────────────────────────────────────────────────────────
2399
+ /**
2400
+ * Specific jobs, by `shortId`. The one facet that names jobs outright rather
2401
+ * than narrowing by an attribute of them.
2402
+ *
2403
+ * It exists for a HAND-PICKED set: a group whose jobs an admin chose
2404
+ * individually stores exactly this list as `group.jobIds`, so the field and
2405
+ * the facet share a name and the stored data needs no translation.
2406
+ *
2407
+ * Case is LOAD-BEARING and must not be normalised: a shortId is mixed case
2408
+ * (`tI8wnSIgRG5O`), so the uppercase treatment country and state codes get
2409
+ * would turn every lookup into a match on nothing.
2410
+ */
2411
+ jobIds: tokens("Jobs"),
2412
+ // ── Skills and company ──────────────────────────────────────────────────────
2413
+ //
2414
+ // `widget` is a RENDERING hint for whoever builds a form from this schema: it
2415
+ // names the picker that produces values this facet can actually match, and
2416
+ // nothing validates differently because of it. A form generated without one
2417
+ // falls back to a free text box, which is what every admin filter was doing —
2418
+ // so "Angular" was typed where the stored skill is "angular" and the filter
2419
+ // narrowed to nothing while looking correctly configured.
2420
+ skills: tokens("Skills").meta({ widget: "skill" }),
2421
+ /** Company page SLUGS: a display name breaks the day a company renames itself. */
2422
+ companies: tokens("Companies").meta({ widget: "company" }),
2423
+ /**
2424
+ * Company display names, for callers that only have one. Prefer `companies`.
2425
+ *
2426
+ * `hidden`: not a second control in a generated form. An admin picking a
2427
+ * company gets the page, and `companies` (its slug) is the stable way to name
2428
+ * it — a display name stops matching the day a company renames itself. This
2429
+ * stays in the vocabulary for API callers who genuinely only hold a name.
2430
+ */
2431
+ companyNames: tokens("Company Names").meta({ widget: "hidden" }),
2432
+ // ── Location ────────────────────────────────────────────────────────────────
2433
+ //
2434
+ // Four facets, THREE controls. They are independent OR-lists: jobs-service
2435
+ // matches each path separately, so `countryCodes` alone asks "anywhere in
2436
+ // India" and `cities` alone asks "in Pune, wherever that is". Collapsing them
2437
+ // into one object facet would lose exactly that, and lose the cross-entry
2438
+ // reading ("has an India office OR a London office") with it.
2439
+ //
2440
+ // What they must NOT be is free text. A country here is `IN`, not "India",
2441
+ // and a state code is a BARE `KA`, not `IN-KA` — type either long form and
2442
+ // the filter matches nothing while looking correctly configured.
2443
+ //
2444
+ // So each names the GEO reference autocomplete (common-service `/geo/*`, a
2445
+ // closed vocabulary) and the field of the picked row it stores. `geoLevel`
2446
+ // says which endpoint to search and `geoField` which key lands in the facet.
2447
+ //
2448
+ // THREE controls, not four: a picked state carries its name and its code, so
2449
+ // the old separate "State Codes" box asked twice for one fact. See `states`.
2450
+ //
2451
+ // Geoapify's `/locations` is NOT the vocabulary here. It geocodes free-text
2452
+ // addresses — the right tool for "where is this job", the wrong one for a
2453
+ // filter, since it is a paid call per keystroke and its results are open.
2454
+ /** ISO 3166-1 alpha-2, uppercase. */
2455
+ countryCodes: codeTokens("Countries").meta({
2456
+ group: GEO_GROUP,
2457
+ widget: "geo",
2458
+ geoLevel: "country",
2459
+ geoField: "countryCode"
2460
+ }),
2461
+ /** Stored BARE (`KA`, not `IN-KA`) — see @todo STATE-CODE-ISO-PREFIX. */
2462
+ stateCodes: codeTokens("States").meta({
2463
+ group: GEO_GROUP,
2464
+ widget: "geo",
2465
+ geoLevel: "state",
2466
+ geoField: "stateCode"
2467
+ }),
2468
+ /**
2469
+ * State NAMES, kept as a facet but WITHOUT a control of its own.
2470
+ *
2471
+ * There is no second box to fill: a picked state carries its name and its
2472
+ * code together, so asking an admin for both was asking twice for one fact.
2473
+ * The picker writes `stateCodes`, which is where the data is going.
2474
+ *
2475
+ * The facet stays because jobs still hold state names — 15,483 carry a name
2476
+ * with NO code — and a saved campaign may already constrain on it. Removing
2477
+ * it would silently drop that constraint from those campaigns. It becomes
2478
+ * unreachable from the UI and is settled by the data migration.
2479
+ *
2480
+ * @todo STATE-CODE-ISO-PREFIX — retire once every job carries a stateCode.
2481
+ */
2482
+ states: tokens("States").meta({ group: GEO_GROUP, widget: "hidden" }),
2483
+ /**
2484
+ * City NAMES, and the one facet here that does not match on a code.
2485
+ *
2486
+ * A name is a display label, not a matching key: it is not unique (99 Indian
2487
+ * city names repeat in the reference data) and not stable (Bengaluru was
2488
+ * Bangalore). Every other facet here matches a code for exactly that reason.
2489
+ *
2490
+ * Cities cannot yet. There is no city code to match ON either side: the
2491
+ * reference dataset carries none, and no job stores one — `LocationSchema`
2492
+ * has country/state codes and a city NAME. A `cityCodes` facet would match
2493
+ * zero of 82,875 jobs on day one.
2494
+ *
2495
+ * So this stays on the name, and the picker does the work a code would: the
2496
+ * admin selects a real city from the reference data, so the stored string is
2497
+ * guaranteed to be a spelling that exists rather than a typo that silently
2498
+ * matches nothing.
2499
+ *
2500
+ * @todo CITY-CODE-UNLOCODE — the fix is UN/LOCODE (or IATA) as the city key,
2501
+ * and it is a data programme, not a facet change: ingest and reconcile the
2502
+ * codes against the 152,970 reference rows, add `cityCode` to
2503
+ * `LocationSchema`, backfill the ~81,000 jobs that carry only a name, and fix
2504
+ * the write paths that emit it. Only then can this match on a code.
2505
+ */
2506
+ cities: tokens("Cities").meta({
2507
+ group: GEO_GROUP,
2508
+ widget: "geo",
2509
+ geoLevel: "city",
2510
+ geoField: "name"
2511
+ }),
2512
+ // ── Flags ───────────────────────────────────────────────────────────────────
2513
+ // No `.default(false)`: an unchecked box must mean "no constraint", not "only
2514
+ // the non-featured ones", so absent simply omits the clause.
2515
+ isFeatured: (0, import_yup21.boolean)().optional().label("Featured Only"),
2516
+ isPromoted: (0, import_yup21.boolean)().optional().label("Promoted Only"),
2517
+ featuredMarkets: tokens("Featured Markets"),
2518
+ // ── Ranges ──────────────────────────────────────────────────────────────────
2519
+ /**
2520
+ * Matched as an OVERLAP against the job's advertised band, not as a bound on
2521
+ * one end: `{min}` keeps jobs whose `salaryRange.max` reaches it.
2522
+ */
2523
+ // NOT `currencyBound`: that guards the USER filter, where several currencies
2524
+ // can be selected and a bound is meaningless across them. Here the currency is
2525
+ // a single sibling field, so the bound is plain and the currency simply
2526
+ // narrows which jobs are compared.
2527
+ salary: (0, import_yup21.object)({
2528
+ min: (0, import_yup21.number)().integer().min(0).optional().default(void 0).label("Salary \u2265"),
2529
+ max: (0, import_yup21.number)().integer().min(0).optional().default(void 0).label("Salary \u2264"),
2530
+ currency: (0, import_yup21.string)().oneOf(SupportedSalaryCurrencies).optional().label("Currency")
2531
+ }).optional().default(void 0).label("Salary"),
2532
+ /** Posted within this many days. Relative, so a saved filter does not rot. */
2533
+ postedWithinDays: days("Posted Within (days)"),
2534
+ /**
2535
+ * Absolute posting window, for callers that want bounds rather than the
2536
+ * relative `postedWithinDays`. EPOCH MS, matching how the field is stored — a
2537
+ * coerced date string would not compare.
2538
+ */
2539
+ createdAt: (0, import_yup21.object)({
2540
+ min: epoch("Posted After"),
2541
+ max: epoch("Posted Before")
2542
+ }).optional().default(void 0).label("Posted Between")
2543
+ }).label("Job Filter");
2544
+ var JobFilterSchema = rejectUnknownKeys(
2545
+ JobFilterShape,
2546
+ () => Object.keys(JobFilterShape.fields)
2547
+ );
2548
+
2032
2549
  // src/job/job-taxonomy.util.ts
2033
2550
  var INDUSTRY_OF_SUB = new Map(
2034
2551
  JOB_TAXONOMY.flatMap(
@@ -2073,7 +2590,7 @@ function deriveIndustry(category, subCategories) {
2073
2590
  }
2074
2591
 
2075
2592
  // src/post/post.schema.ts
2076
- var import_yup19 = require("yup");
2593
+ var import_yup22 = require("yup");
2077
2594
 
2078
2595
  // src/post/post.constant.ts
2079
2596
  var PostStatus = /* @__PURE__ */ ((PostStatus2) => {
@@ -2085,177 +2602,540 @@ var PostStatus = /* @__PURE__ */ ((PostStatus2) => {
2085
2602
  var SupportedPostStatuses = Object.values(PostStatus);
2086
2603
 
2087
2604
  // src/post/post.schema.ts
2088
- var PostSchema = (0, import_yup19.object)({
2605
+ var PostSchema = (0, import_yup22.object)({
2089
2606
  /** Headline shown in listings and at the top of the post. */
2090
- title: (0, import_yup19.string)().required().min(5).max(200).label("Title"),
2607
+ title: (0, import_yup22.string)().required().min(5).max(200).label("Title"),
2091
2608
  /** User-friendly, unique URL identifier. */
2092
- slug: (0, import_yup19.string)().required().max(250).label("Slug"),
2609
+ slug: (0, import_yup22.string)().required().max(250).label("Slug"),
2093
2610
  /** Short summary used in listings and meta description fallback. */
2094
- excerpt: (0, import_yup19.string)().max(500).optional().label("Excerpt"),
2611
+ excerpt: (0, import_yup22.string)().max(500).optional().label("Excerpt"),
2095
2612
  /** Rendered HTML source of truth for the post body (sanitized on write/render).
2096
2613
  * Supports rich formatting markdown can't express (image size/alignment, etc). */
2097
- bodyHTML: (0, import_yup19.string)().required().min(1).label("Body (HTML)"),
2614
+ bodyHTML: (0, import_yup22.string)().required().min(1).label("Body (HTML)"),
2098
2615
  /** Optional Markdown mirror of the body (portable text / search fallback).
2099
2616
  * Kept for back-compat and plain-text derivation; bodyHTML is authoritative. */
2100
- bodyMarkdown: (0, import_yup19.string)().optional().label("Body (Markdown)"),
2617
+ bodyMarkdown: (0, import_yup22.string)().optional().label("Body (Markdown)"),
2101
2618
  /** Cover/hero image URL. Not `.url()`-validated: the value is either an
2102
2619
  * uploaded file URL served by this platform (which may be a bare host like
2103
2620
  * `http://localhost:3082/...` in dev, that yup's `.url()` wrongly rejects) or
2104
2621
  * an admin-pasted link. Kept as a plain string; empty ⇒ null. */
2105
- coverImage: (0, import_yup19.string)().nullable().optional().label("Cover image"),
2622
+ coverImage: (0, import_yup22.string)().nullable().optional().label("Cover image"),
2106
2623
  /** Author (user id). */
2107
- authorId: (0, import_yup19.string)().required().label("Author ID"),
2624
+ authorId: (0, import_yup22.string)().required().label("Author ID"),
2108
2625
  /** Free-form tags for filtering and related-posts. */
2109
- tags: (0, import_yup19.array)().of((0, import_yup19.string)().trim().required()).default([]).label("Tags"),
2626
+ tags: (0, import_yup22.array)().of((0, import_yup22.string)().trim().required()).default([]).label("Tags"),
2110
2627
  /** Estimated reading time in minutes (server-derived from body length). */
2111
- readingTimeMinutes: (0, import_yup19.number)().min(0).optional().label("Reading time (minutes)"),
2628
+ readingTimeMinutes: (0, import_yup22.number)().min(0).optional().label("Reading time (minutes)"),
2112
2629
  /** SEO overrides. */
2113
- seoTags: (0, import_yup19.object)({
2114
- description: (0, import_yup19.string)().optional().label("Description"),
2115
- keywords: (0, import_yup19.array)().of((0, import_yup19.string)().required()).optional().label("Keywords")
2630
+ seoTags: (0, import_yup22.object)({
2631
+ description: (0, import_yup22.string)().optional().label("Description"),
2632
+ keywords: (0, import_yup22.array)().of((0, import_yup22.string)().required()).optional().label("Keywords")
2116
2633
  }).nullable().optional().label("SEO Tags"),
2117
2634
  /** When the post was first published (epoch ms). Server-owned. */
2118
- publishedAt: (0, import_yup19.number)().nullable().optional().label("Published at"),
2635
+ publishedAt: (0, import_yup22.number)().nullable().optional().label("Published at"),
2119
2636
  /** Whether the post is featured in the blog listing. */
2120
- isFeatured: (0, import_yup19.boolean)().default(false).optional().label("Is featured"),
2637
+ isFeatured: (0, import_yup22.boolean)().default(false).optional().label("Is featured"),
2121
2638
  /** Publication state. */
2122
- status: (0, import_yup19.string)().oneOf(SupportedPostStatuses).default("draft" /* Draft */).required().label("Status")
2639
+ status: (0, import_yup22.string)().oneOf(SupportedPostStatuses).default("draft" /* Draft */).required().label("Status")
2123
2640
  }).concat(DbDefaultSchema).noUnknown().label("Post");
2124
2641
 
2125
- // src/marketing/marketing.schema.ts
2126
- var import_yup20 = require("yup");
2642
+ // src/geo/geo.constant.ts
2643
+ var GeoKind = /* @__PURE__ */ ((GeoKind2) => {
2644
+ GeoKind2["Country"] = "country";
2645
+ GeoKind2["State"] = "state";
2646
+ GeoKind2["City"] = "city";
2647
+ return GeoKind2;
2648
+ })(GeoKind || {});
2649
+ var SupportedGeoKinds = Object.values(GeoKind);
2650
+ var GeoSyncStatus = /* @__PURE__ */ ((GeoSyncStatus3) => {
2651
+ GeoSyncStatus3["Pending"] = "pending";
2652
+ GeoSyncStatus3["Running"] = "running";
2653
+ GeoSyncStatus3["Succeeded"] = "succeeded";
2654
+ GeoSyncStatus3["Failed"] = "failed";
2655
+ GeoSyncStatus3["Skipped"] = "skipped";
2656
+ return GeoSyncStatus3;
2657
+ })(GeoSyncStatus || {});
2658
+ var SupportedGeoSyncStatuses = Object.values(GeoSyncStatus);
2659
+ var TerminalGeoSyncStatuses = [
2660
+ "succeeded" /* Succeeded */,
2661
+ "failed" /* Failed */,
2662
+ "skipped" /* Skipped */
2663
+ ];
2664
+ var GeoSyncPhase = /* @__PURE__ */ ((GeoSyncPhase3) => {
2665
+ GeoSyncPhase3["Checking"] = "checking";
2666
+ GeoSyncPhase3["Countries"] = "countries";
2667
+ GeoSyncPhase3["States"] = "states";
2668
+ GeoSyncPhase3["Cities"] = "cities";
2669
+ GeoSyncPhase3["Indexing"] = "indexing";
2670
+ GeoSyncPhase3["Done"] = "done";
2671
+ return GeoSyncPhase3;
2672
+ })(GeoSyncPhase || {});
2673
+ var SupportedGeoSyncPhases = Object.values(GeoSyncPhase);
2674
+ var GeoSyncTrigger = /* @__PURE__ */ ((GeoSyncTrigger3) => {
2675
+ GeoSyncTrigger3["Manual"] = "manual";
2676
+ GeoSyncTrigger3["Boot"] = "boot";
2677
+ return GeoSyncTrigger3;
2678
+ })(GeoSyncTrigger || {});
2679
+ var SupportedGeoSyncTriggers = Object.values(GeoSyncTrigger);
2680
+ var GEO_COLLECTIONS = {
2681
+ countries: "geo_countries",
2682
+ states: "geo_states",
2683
+ cities: "geo_cities",
2684
+ syncRuns: "geo_sync_runs"
2685
+ };
2686
+ var GEO_SEARCH_MAX = 64;
2127
2687
 
2128
- // src/user/user.constant.ts
2129
- var UserRole = /* @__PURE__ */ ((UserRole2) => {
2130
- UserRole2["Admin"] = "admin";
2131
- UserRole2["Moderator"] = "moderator";
2132
- UserRole2["Manager"] = "manager";
2133
- UserRole2["Recruiter"] = "recruiter";
2134
- UserRole2["JobSeeker"] = "job_seeker";
2135
- UserRole2["Coordinator"] = "coordinator";
2136
- UserRole2["System"] = "system";
2137
- UserRole2["Normal"] = "normal";
2138
- return UserRole2;
2139
- })(UserRole || {});
2140
- var SupportedUserRoles = Object.values(UserRole);
2141
- var UserStatus = /* @__PURE__ */ ((UserStatus2) => {
2142
- UserStatus2["Active"] = "active";
2143
- UserStatus2["Blocked"] = "blocked";
2144
- UserStatus2["Pending"] = "pending";
2145
- UserStatus2["Deleted"] = "deleted";
2146
- UserStatus2["MarkedForDeletion"] = "marked_for_deletion";
2147
- return UserStatus2;
2148
- })(UserStatus || {});
2149
- var SupportedUserStatuses = Object.values(UserStatus);
2150
- var DefaultUserRoles = ["normal" /* Normal */];
2151
- var UserProfileVisibility = /* @__PURE__ */ ((UserProfileVisibility2) => {
2152
- UserProfileVisibility2["Public"] = "public";
2153
- UserProfileVisibility2["Private"] = "private";
2154
- UserProfileVisibility2["Hidden"] = "hidden";
2155
- return UserProfileVisibility2;
2156
- })(UserProfileVisibility || {});
2157
- var SupportedUserProfileVisibilities = Object.values(
2158
- UserProfileVisibility
2688
+ // src/geo/country.schema.ts
2689
+ var import_yup23 = require("yup");
2690
+ var CountrySchema = (0, import_yup23.object)({
2691
+ /** The upstream dr5hn id. Stable, and the key every upsert matches on. */
2692
+ id: (0, import_yup23.number)().integer().required().label("ID"),
2693
+ name: (0, import_yup23.string)().trim().required().label("Name"),
2694
+ /**
2695
+ * ISO 3166-1 alpha-2, UPPERCASE (`IN`, `SE`).
2696
+ *
2697
+ * Uppercased on ingest, because every filter facet in this package
2698
+ * uppercase-normalizes codes (`codeTokens`) and stored job/user documents are
2699
+ * already clean uppercase alpha-2. A lowercase value here would compare
2700
+ * unequal to all of them and silently match nothing.
2701
+ */
2702
+ countryCode: (0, import_yup23.string)().trim().uppercase().length(2).required().label("Country Code"),
2703
+ iso3: (0, import_yup23.string)().trim().uppercase().length(3).optional().nullable().label("ISO3"),
2704
+ numericCode: (0, import_yup23.string)().trim().optional().nullable().label("Numeric Code"),
2705
+ phonecode: (0, import_yup23.string)().trim().optional().nullable().label("Phone Code"),
2706
+ capital: (0, import_yup23.string)().trim().optional().nullable().label("Capital"),
2707
+ currency: (0, import_yup23.string)().trim().optional().nullable().label("Currency"),
2708
+ currencyName: (0, import_yup23.string)().trim().optional().nullable().label("Currency Name"),
2709
+ currencySymbol: (0, import_yup23.string)().trim().optional().nullable().label("Currency Symbol"),
2710
+ tld: (0, import_yup23.string)().trim().optional().nullable().label("TLD"),
2711
+ /** The name in its own script, e.g. "भारत". Stored, but NOT searchable; see `searchByLocale`. */
2712
+ native: (0, import_yup23.string)().trim().optional().nullable().label("Native Name"),
2713
+ population: (0, import_yup23.number)().optional().nullable().label("Population"),
2714
+ gdp: (0, import_yup23.number)().optional().nullable().label("GDP"),
2715
+ region: (0, import_yup23.string)().trim().optional().nullable().label("Region"),
2716
+ regionId: (0, import_yup23.number)().integer().optional().nullable().label("Region ID"),
2717
+ subregion: (0, import_yup23.string)().trim().optional().nullable().label("Subregion"),
2718
+ subregionId: (0, import_yup23.number)().integer().optional().nullable().label("Subregion ID"),
2719
+ nationality: (0, import_yup23.string)().trim().optional().nullable().label("Nationality"),
2720
+ areaSqKm: (0, import_yup23.number)().optional().nullable().label("Area (sq km)"),
2721
+ postalCodeFormat: (0, import_yup23.string)().trim().optional().nullable().label("Postal Code Format"),
2722
+ postalCodeRegex: (0, import_yup23.string)().optional().nullable().label("Postal Code Regex"),
2723
+ timezones: (0, import_yup23.array)().of(
2724
+ (0, import_yup23.object)({
2725
+ zoneName: (0, import_yup23.string)().optional().nullable().label("Zone Name"),
2726
+ gmtOffset: (0, import_yup23.number)().optional().nullable().label("GMT Offset"),
2727
+ gmtOffsetName: (0, import_yup23.string)().optional().nullable().label("GMT Offset Name"),
2728
+ abbreviation: (0, import_yup23.string)().optional().nullable().label("Abbreviation"),
2729
+ tzName: (0, import_yup23.string)().optional().nullable().label("TZ Name")
2730
+ })
2731
+ ).optional().nullable().label("Timezones"),
2732
+ /**
2733
+ * Localized names keyed by locale (`{ sv: "Indien", fr: "Inde", … }`), ~19 of
2734
+ * them. Deliberately UNSHAPED: the key set is upstream's to change, and
2735
+ * declaring it here would turn a new locale into a validation failure. The
2736
+ * VALUES are split per-locale into `searchByLocale`.
2737
+ */
2738
+ translations: (0, import_yup23.object)().optional().nullable().label("Translations"),
2739
+ /**
2740
+ * Coordinates as NUMBERS.
2741
+ *
2742
+ * Upstream ships these as strings (`"33.00000000"`). They are coerced on
2743
+ * ingest, because a range query against a string compares lexicographically:
2744
+ * `"9" > "10"` is true, so a bounding-box filter would be quietly wrong rather
2745
+ * than failing loudly.
2746
+ */
2747
+ latitude: (0, import_yup23.number)().optional().nullable().label("Latitude"),
2748
+ longitude: (0, import_yup23.number)().optional().nullable().label("Longitude"),
2749
+ emoji: (0, import_yup23.string)().optional().nullable().label("Emoji"),
2750
+ emojiU: (0, import_yup23.string)().optional().nullable().label("Emoji Unicode"),
2751
+ /**
2752
+ * The name field searched for the DEFAULT (English) locale: the canonical
2753
+ * `name`, and nothing else.
2754
+ *
2755
+ * Search is scoped to ONE locale, so the searchable names are split across
2756
+ * this field and `searchByLocale` rather than pooled into one array. A pooled
2757
+ * field would make every locale's search implicitly a search of all 19
2758
+ * languages: a French UI searching "Inde" would also match German "Indien".
2759
+ */
2760
+ searchDefault: (0, import_yup23.array)().of((0, import_yup23.string)().required()).optional().default([]).label("Search Default"),
2761
+ /**
2762
+ * Per-locale name fields, keyed by lowercased BCP-47 tag (`de`, `pt-br`).
2763
+ * Each carries ONLY that locale's translation, which is what makes
2764
+ * `?locale=de` German and not "German plus everything that looks similar".
2765
+ *
2766
+ * Unshaped for the same reason as `translations`: the key set is upstream's.
2767
+ *
2768
+ * `native` is deliberately NOT indexed here. It cannot be attributed to a
2769
+ * language with this data — 84% of city native names equal SEVERAL locales'
2770
+ * translations (Latin spellings coinciding across de/it/nl/hr/pl) while
2771
+ * distinctive non-Latin names like `भारत` match none — so filing `कर्नाटक`
2772
+ * under German would be worse than not indexing it at all.
2773
+ *
2774
+ * The 19 upstream locales are br, ko, pt-BR, pt, nl, hr, fa, de, es, fr, ja,
2775
+ * it, zh-CN, tr, ru, uk, pl, hi, ar. **`sv` is absent**, so a Swedish search
2776
+ * matches nothing. That is the accepted cost of strict scoping.
2777
+ */
2778
+ searchByLocale: (0, import_yup23.object)().optional().nullable().label("Search By Locale"),
2779
+ /** Epoch ms of the run that last wrote this row. Drives the stale-row prune. */
2780
+ syncedAt: (0, import_yup23.number)().optional().nullable().label("Synced At")
2781
+ }).label("Country");
2782
+
2783
+ // src/geo/state.schema.ts
2784
+ var import_yup24 = require("yup");
2785
+ var StateSchema = (0, import_yup24.object)({
2786
+ /** The upstream dr5hn id. Stable, and the key every upsert matches on. */
2787
+ id: (0, import_yup24.number)().integer().required().label("ID"),
2788
+ name: (0, import_yup24.string)().trim().required().label("Name"),
2789
+ countryId: (0, import_yup24.number)().integer().optional().nullable().label("Country ID"),
2790
+ /** ISO 3166-1 alpha-2 of the parent, UPPERCASE. See `CountrySchema.countryCode`. */
2791
+ countryCode: (0, import_yup24.string)().trim().uppercase().length(2).required().label("Country Code"),
2792
+ countryName: (0, import_yup24.string)().trim().optional().nullable().label("Country Name"),
2793
+ /**
2794
+ * The BARE subdivision code, e.g. `KA` (upstream `iso2`).
2795
+ *
2796
+ * `@todo STATE-CODE-ISO-PREFIX` — this is **not globally unique**: `BDS`, `CA`,
2797
+ * `NL` and `SP` all recur across several countries, so it is only meaningful
2798
+ * PAIRED with `countryCode`. That is why the cities route is nested under both
2799
+ * codes rather than under a state code alone, which would silently serve
2800
+ * another country's cities. `stateCodeIso` below is the migration target;
2801
+ * both are stored so the migration has its destination present before any
2802
+ * consumer moves.
2803
+ */
2804
+ stateCode: (0, import_yup24.string)().trim().uppercase().optional().nullable().label("State Code"),
2805
+ /** Fully-qualified ISO 3166-2, e.g. `IN-KA` (upstream `iso3166_2`). */
2806
+ stateCodeIso: (0, import_yup24.string)().trim().uppercase().optional().nullable().label("ISO 3166-2 Code"),
2807
+ fipsCode: (0, import_yup24.string)().trim().optional().nullable().label("FIPS Code"),
2808
+ /** Upstream's own word for the subdivision: "province", "state", "district", … */
2809
+ type: (0, import_yup24.string)().trim().optional().nullable().label("Type"),
2810
+ level: (0, import_yup24.number)().integer().optional().nullable().label("Level"),
2811
+ parentId: (0, import_yup24.number)().integer().optional().nullable().label("Parent ID"),
2812
+ /** The name in its own script. Stored, but NOT searchable; see `searchByLocale`. */
2813
+ native: (0, import_yup24.string)().trim().optional().nullable().label("Native Name"),
2814
+ /** Numbers, not the strings upstream ships. See `CountrySchema.latitude`. */
2815
+ latitude: (0, import_yup24.number)().optional().nullable().label("Latitude"),
2816
+ longitude: (0, import_yup24.number)().optional().nullable().label("Longitude"),
2817
+ timezone: (0, import_yup24.string)().trim().optional().nullable().label("Timezone"),
2818
+ /** Localized names keyed by locale. Unshaped; see `CountrySchema.translations`. */
2819
+ translations: (0, import_yup24.object)().optional().nullable().label("Translations"),
2820
+ /**
2821
+ * The name field searched for the DEFAULT (English) locale: the canonical
2822
+ * `name`, and nothing else.
2823
+ *
2824
+ * Search is scoped to ONE locale, so the searchable names are split across
2825
+ * this field and `searchByLocale` rather than pooled into one array. A pooled
2826
+ * field would make every locale's search implicitly a search of all 19
2827
+ * languages: a French UI searching "Inde" would also match German "Indien".
2828
+ */
2829
+ searchDefault: (0, import_yup24.array)().of((0, import_yup24.string)().required()).optional().default([]).label("Search Default"),
2830
+ /**
2831
+ * Per-locale name fields, keyed by lowercased BCP-47 tag (`de`, `pt-br`).
2832
+ * Each carries ONLY that locale's translation, which is what makes
2833
+ * `?locale=de` German and not "German plus everything that looks similar".
2834
+ *
2835
+ * Unshaped for the same reason as `translations`: the key set is upstream's.
2836
+ *
2837
+ * `native` is deliberately NOT indexed here. It cannot be attributed to a
2838
+ * language with this data — 84% of city native names equal SEVERAL locales'
2839
+ * translations (Latin spellings coinciding across de/it/nl/hr/pl) while
2840
+ * distinctive non-Latin names like `भारत` match none — so filing `कर्नाटक`
2841
+ * under German would be worse than not indexing it at all.
2842
+ *
2843
+ * The 19 upstream locales are br, ko, pt-BR, pt, nl, hr, fa, de, es, fr, ja,
2844
+ * it, zh-CN, tr, ru, uk, pl, hi, ar. **`sv` is absent**, so a Swedish search
2845
+ * matches nothing. That is the accepted cost of strict scoping.
2846
+ */
2847
+ searchByLocale: (0, import_yup24.object)().optional().nullable().label("Search By Locale"),
2848
+ /** Epoch ms of the run that last wrote this row. Drives the stale-row prune. */
2849
+ syncedAt: (0, import_yup24.number)().optional().nullable().label("Synced At")
2850
+ }).label("State");
2851
+
2852
+ // src/geo/city.schema.ts
2853
+ var import_yup25 = require("yup");
2854
+ var CitySchema = (0, import_yup25.object)({
2855
+ /** The upstream dr5hn id. Stable, and the key every upsert matches on. */
2856
+ id: (0, import_yup25.number)().integer().required().label("ID"),
2857
+ name: (0, import_yup25.string)().trim().required().label("Name"),
2858
+ stateId: (0, import_yup25.number)().integer().optional().nullable().label("State ID"),
2859
+ /** The BARE subdivision code. Only meaningful paired with `countryCode`; see `StateSchema`. */
2860
+ stateCode: (0, import_yup25.string)().trim().uppercase().optional().nullable().label("State Code"),
2861
+ /**
2862
+ * Fully-qualified ISO 3166-2, e.g. `IN-KA`.
2863
+ *
2864
+ * DERIVED on ingest as `${countryCode}-${stateCode}`, because the upstream city
2865
+ * record carries only the bare `state_code` — unlike the state record, which
2866
+ * ships `iso3166_2` directly. Stored anyway so the `@todo STATE-CODE-ISO-PREFIX`
2867
+ * migration can re-point the cities route without a 153k-row re-ingest.
2868
+ * Null when either half is missing.
2869
+ */
2870
+ stateCodeIso: (0, import_yup25.string)().trim().uppercase().optional().nullable().label("ISO 3166-2 Code"),
2871
+ stateName: (0, import_yup25.string)().trim().optional().nullable().label("State Name"),
2872
+ countryId: (0, import_yup25.number)().integer().optional().nullable().label("Country ID"),
2873
+ /** ISO 3166-1 alpha-2 of the parent, UPPERCASE. See `CountrySchema.countryCode`. */
2874
+ countryCode: (0, import_yup25.string)().trim().uppercase().length(2).required().label("Country Code"),
2875
+ countryName: (0, import_yup25.string)().trim().optional().nullable().label("Country Name"),
2876
+ /** Numbers, not the strings upstream ships. See `CountrySchema.latitude`. */
2877
+ latitude: (0, import_yup25.number)().optional().nullable().label("Latitude"),
2878
+ longitude: (0, import_yup25.number)().optional().nullable().label("Longitude"),
2879
+ /** The name in its own script. Stored, but NOT searchable; see `searchByLocale`. */
2880
+ native: (0, import_yup25.string)().trim().optional().nullable().label("Native Name"),
2881
+ type: (0, import_yup25.string)().trim().optional().nullable().label("Type"),
2882
+ level: (0, import_yup25.number)().integer().optional().nullable().label("Level"),
2883
+ parentId: (0, import_yup25.number)().integer().optional().nullable().label("Parent ID"),
2884
+ /**
2885
+ * Null on most rows. Worth keeping (it is a good relevance signal when
2886
+ * present) but never worth SORTING by alone, since a null-heavy sort buries
2887
+ * every city upstream has no figure for.
2888
+ */
2889
+ population: (0, import_yup25.number)().optional().nullable().label("Population"),
2890
+ timezone: (0, import_yup25.string)().trim().optional().nullable().label("Timezone"),
2891
+ /** Localized names keyed by locale. Unshaped; see `CountrySchema.translations`. */
2892
+ translations: (0, import_yup25.object)().optional().nullable().label("Translations"),
2893
+ /**
2894
+ * The name field searched for the DEFAULT (English) locale: the canonical
2895
+ * `name`, and nothing else.
2896
+ *
2897
+ * Search is scoped to ONE locale, so the searchable names are split across
2898
+ * this field and `searchByLocale` rather than pooled into one array. A pooled
2899
+ * field would make every locale's search implicitly a search of all 19
2900
+ * languages: a French UI searching "Inde" would also match German "Indien".
2901
+ */
2902
+ searchDefault: (0, import_yup25.array)().of((0, import_yup25.string)().required()).optional().default([]).label("Search Default"),
2903
+ /**
2904
+ * Per-locale name fields, keyed by lowercased BCP-47 tag (`de`, `pt-br`).
2905
+ * Each carries ONLY that locale's translation, which is what makes
2906
+ * `?locale=de` German and not "German plus everything that looks similar".
2907
+ *
2908
+ * Unshaped for the same reason as `translations`: the key set is upstream's.
2909
+ *
2910
+ * `native` is deliberately NOT indexed here. It cannot be attributed to a
2911
+ * language with this data — 84% of city native names equal SEVERAL locales'
2912
+ * translations (Latin spellings coinciding across de/it/nl/hr/pl) while
2913
+ * distinctive non-Latin names like `भारत` match none — so filing `कर्नाटक`
2914
+ * under German would be worse than not indexing it at all.
2915
+ *
2916
+ * The 19 upstream locales are br, ko, pt-BR, pt, nl, hr, fa, de, es, fr, ja,
2917
+ * it, zh-CN, tr, ru, uk, pl, hi, ar. **`sv` is absent**, so a Swedish search
2918
+ * matches nothing. That is the accepted cost of strict scoping.
2919
+ */
2920
+ searchByLocale: (0, import_yup25.object)().optional().nullable().label("Search By Locale"),
2921
+ /** Epoch ms of the run that last wrote this row. Drives the stale-row prune. */
2922
+ syncedAt: (0, import_yup25.number)().optional().nullable().label("Synced At")
2923
+ }).label("City");
2924
+
2925
+ // src/geo/country-filter.schema.ts
2926
+ var import_yup26 = require("yup");
2927
+ var CountryFilterShape = (0, import_yup26.object)({
2928
+ /** ISO 3166-1 alpha-2 codes, e.g. `IN`. Uppercased, matching how they are stored. */
2929
+ countryCodes: codeTokens("Country Codes"),
2930
+ /** ISO 3166-1 alpha-3 codes, e.g. `IND`. */
2931
+ iso3s: codeTokens("ISO3 Codes"),
2932
+ /**
2933
+ * `tokens`, NOT `codeTokens`: a region is a display string ("Asia", "Europe"),
2934
+ * and uppercasing it would match nothing.
2935
+ */
2936
+ regions: tokens("Regions"),
2937
+ subregions: tokens("Subregions"),
2938
+ /** Display names, matched exactly. Free-text matching is `?search=`. */
2939
+ names: tokens("Names")
2940
+ }).label("Country Filter");
2941
+ var CountryFilterSchema = rejectUnknownKeys(
2942
+ CountryFilterShape,
2943
+ () => Object.keys(CountryFilterShape.fields)
2159
2944
  );
2160
- var SignupContextSource = /* @__PURE__ */ ((SignupContextSource2) => {
2161
- SignupContextSource2["CfHeader"] = "cf-header";
2162
- SignupContextSource2["Location"] = "location";
2163
- SignupContextSource2["Default"] = "default";
2164
- return SignupContextSource2;
2165
- })(SignupContextSource || {});
2166
- var SupportedSignupContextSources = Object.values(SignupContextSource);
2167
- var UserDetailType = /* @__PURE__ */ ((UserDetailType2) => {
2168
- UserDetailType2["Overview"] = "overview";
2169
- UserDetailType2["AdditionalInfo"] = "additionalInfo";
2170
- UserDetailType2["General"] = "general";
2171
- UserDetailType2["SocialAccounts"] = "socialAccounts";
2172
- UserDetailType2["WorkExperiences"] = "workExperiences";
2173
- UserDetailType2["Educations"] = "educations";
2174
- UserDetailType2["Skills"] = "skills";
2175
- UserDetailType2["Languages"] = "languages";
2176
- UserDetailType2["Certifications"] = "certifications";
2177
- UserDetailType2["Interests"] = "interests";
2178
- UserDetailType2["Projects"] = "projects";
2179
- return UserDetailType2;
2180
- })(UserDetailType || {});
2181
- var ProficiencyLevel = /* @__PURE__ */ ((ProficiencyLevel2) => {
2182
- ProficiencyLevel2["BasicAwareness"] = "basic_awareness";
2183
- ProficiencyLevel2["Beginner"] = "beginner";
2184
- ProficiencyLevel2["Intermediate"] = "intermediate";
2185
- ProficiencyLevel2["Advanced"] = "advanced";
2186
- ProficiencyLevel2["Expert"] = "expert";
2187
- return ProficiencyLevel2;
2188
- })(ProficiencyLevel || {});
2189
- var SupportedProficiencyLevels = Object.values(ProficiencyLevel);
2190
- var JobSearchUrgency = /* @__PURE__ */ ((JobSearchUrgency2) => {
2191
- JobSearchUrgency2["Asap"] = "asap";
2192
- JobSearchUrgency2["WithinThreeMonths"] = "within_3_months";
2193
- JobSearchUrgency2["WithinSixMonths"] = "within_6_months";
2194
- JobSearchUrgency2["PassivelyBrowsing"] = "passively_browsing";
2195
- return JobSearchUrgency2;
2196
- })(JobSearchUrgency || {});
2197
- var SupportedJobSearchUrgencies = Object.values(JobSearchUrgency);
2198
- var ReferralSource = /* @__PURE__ */ ((ReferralSource2) => {
2199
- ReferralSource2["Recruiter"] = "recruiter";
2200
- ReferralSource2["LinkedIn"] = "linkedin";
2201
- ReferralSource2["TikTokInstagram"] = "tiktok_instagram";
2202
- ReferralSource2["Google"] = "google";
2203
- ReferralSource2["Reddit"] = "reddit";
2204
- ReferralSource2["ChatGptGemini"] = "chatgpt_gemini";
2205
- ReferralSource2["Friend"] = "friend";
2206
- ReferralSource2["Other"] = "other";
2207
- return ReferralSource2;
2208
- })(ReferralSource || {});
2209
- var SupportedReferralSources = Object.values(ReferralSource);
2210
- var JobAlertFrequency = /* @__PURE__ */ ((JobAlertFrequency2) => {
2211
- JobAlertFrequency2["Off"] = "off";
2212
- JobAlertFrequency2["Daily"] = "daily";
2213
- JobAlertFrequency2["Weekly"] = "weekly";
2214
- return JobAlertFrequency2;
2215
- })(JobAlertFrequency || {});
2216
- var SupportedJobAlertFrequencies = Object.values(JobAlertFrequency);
2217
- var NotificationChannel = /* @__PURE__ */ ((NotificationChannel2) => {
2218
- NotificationChannel2["GroupActivity"] = "groupActivity";
2219
- NotificationChannel2["JobActivity"] = "jobActivity";
2220
- NotificationChannel2["ApplicationUpdates"] = "applicationUpdates";
2221
- NotificationChannel2["ProductUpdates"] = "productUpdates";
2222
- return NotificationChannel2;
2223
- })(NotificationChannel || {});
2224
- var SupportedNotificationChannels = Object.values(NotificationChannel);
2225
- var SupportedSalaryCurrencies = ["USD", "EUR", "GBP", "SEK", "INR"];
2226
- var MIN_SALARY_LOWER_BOUND = 0;
2227
- var MIN_SALARY_UPPER_BOUND = 1e6;
2228
- var PREFILL_MIRRORED_KEYS = [
2229
- "headline",
2230
- "aboutMe",
2231
- "mobile",
2232
- "location",
2233
- "experienceLevel",
2234
- "socialAccounts",
2235
- "workExperiences",
2236
- "educations"
2237
- ];
2238
- var PRIVATE_PROFILE_FIELDS = [
2239
- "email",
2240
- "emailVerified",
2241
- "mobile",
2242
- "additionalInfo"
2243
- ];
2945
+
2946
+ // src/geo/state-filter.schema.ts
2947
+ var import_yup27 = require("yup");
2948
+ var StateFilterShape = (0, import_yup27.object)({
2949
+ /** ISO 3166-1 alpha-2 of the parent country, e.g. `IN`. */
2950
+ countryCodes: codeTokens("Country Codes"),
2951
+ /**
2952
+ * BARE subdivision codes, e.g. `KA`.
2953
+ *
2954
+ * `@todo STATE-CODE-ISO-PREFIX` — not globally unique, so this facet ALONE is
2955
+ * an ambiguous question: `stateCodes[0]=BDS` matches Badakhshan in Afghanistan
2956
+ * and every other `BDS` elsewhere. Pair it with `countryCodes` to mean one
2957
+ * place. See `StateSchema.stateCode`.
2958
+ */
2959
+ stateCodes: codeTokens("State Codes"),
2960
+ /** Display names, matched exactly. Free-text matching is `?search=`. */
2961
+ names: tokens("Names"),
2962
+ /**
2963
+ * Upstream's own subdivision word: "province", "state", "district", …
2964
+ *
2965
+ * `tokens`, NOT `codeTokens`: these are lowercase display strings and
2966
+ * uppercasing them would match nothing. An OPEN vocabulary rather than an
2967
+ * `enumFacet`, because upstream ships 30-odd values and adding one should not
2968
+ * require a schema release here.
2969
+ */
2970
+ types: tokens("Types")
2971
+ }).label("State Filter");
2972
+ var StateFilterSchema = rejectUnknownKeys(
2973
+ StateFilterShape,
2974
+ () => Object.keys(StateFilterShape.fields)
2975
+ );
2976
+
2977
+ // src/geo/city-filter.schema.ts
2978
+ var import_yup28 = require("yup");
2979
+ var CityFilterShape = (0, import_yup28.object)({
2980
+ /** ISO 3166-1 alpha-2 of the parent country, e.g. `IN`. */
2981
+ countryCodes: codeTokens("Country Codes"),
2982
+ /**
2983
+ * BARE subdivision codes, e.g. `KA`. Ambiguous without `countryCodes`; see
2984
+ * `StateFilterSchema.stateCodes` and `@todo STATE-CODE-ISO-PREFIX`.
2985
+ */
2986
+ stateCodes: codeTokens("State Codes"),
2987
+ /** Display names, matched exactly. Free-text matching is `?search=`. */
2988
+ names: tokens("Names")
2989
+ }).label("City Filter");
2990
+ var CityFilterSchema = rejectUnknownKeys(
2991
+ CityFilterShape,
2992
+ () => Object.keys(CityFilterShape.fields)
2993
+ );
2994
+
2995
+ // src/geo/geo-sync.schema.ts
2996
+ var import_yup29 = require("yup");
2997
+ var GeoSyncCountsSchema = (0, import_yup29.object)({
2998
+ read: (0, import_yup29.number)().integer().min(0).default(0).label("Read"),
2999
+ upserted: (0, import_yup29.number)().integer().min(0).default(0).label("Upserted"),
3000
+ modified: (0, import_yup29.number)().integer().min(0).default(0).label("Modified"),
3001
+ matched: (0, import_yup29.number)().integer().min(0).default(0).label("Matched"),
3002
+ /** Rows removed because upstream dropped them. See `prunedSkipped`. */
3003
+ pruned: (0, import_yup29.number)().integer().min(0).default(0).label("Pruned"),
3004
+ /**
3005
+ * True when the stale-row prune was REFUSED because it would have removed more
3006
+ * than its safety threshold of the collection. A partial fetch that looked
3007
+ * like a success must not be able to empty the table, so the run reports the
3008
+ * refusal rather than acting on it.
3009
+ */
3010
+ prunedSkipped: (0, import_yup29.boolean)().default(false).label("Prune Skipped")
3011
+ }).label("Sync Counts");
3012
+ var GeoSyncRunSchema = (0, import_yup29.object)({
3013
+ jobId: (0, import_yup29.string)().required().label("Job ID"),
3014
+ status: (0, import_yup29.mixed)().oneOf(SupportedGeoSyncStatuses).required().label("Status"),
3015
+ trigger: (0, import_yup29.mixed)().oneOf(SupportedGeoSyncTriggers).optional().label("Trigger"),
3016
+ /** Whether the caller asked to run even though the release tag was unchanged. */
3017
+ force: (0, import_yup29.boolean)().default(false).label("Force"),
3018
+ /**
3019
+ * The upstream release tag this run fetched, e.g. `v3.2-export.7`.
3020
+ *
3021
+ * Null only when the tag could not be read AND `force` was set — a run that
3022
+ * cannot tell what version it is fetching is otherwise a failure, not a
3023
+ * refresh, so it is not allowed to proceed unattended.
3024
+ */
3025
+ releaseTag: (0, import_yup29.string)().optional().nullable().label("Release Tag"),
3026
+ /** The tag of the last successful run, i.e. what this one is replacing. */
3027
+ previousReleaseTag: (0, import_yup29.string)().optional().nullable().label("Previous Release Tag"),
3028
+ startedAt: (0, import_yup29.number)().required().label("Started At"),
3029
+ finishedAt: (0, import_yup29.number)().optional().nullable().label("Finished At"),
3030
+ durationMs: (0, import_yup29.number)().optional().nullable().label("Duration (ms)"),
3031
+ phase: (0, import_yup29.mixed)().oneOf(SupportedGeoSyncPhases).optional().nullable().label("Phase"),
3032
+ /**
3033
+ * Live progress for the dashboard bar.
3034
+ *
3035
+ * `total` is an ESTIMATE — the previous successful run's count for the same
3036
+ * collection, or a hardcoded fallback on the very first run, since the true
3037
+ * total is not known until the stream ends. The UI should render it as one.
3038
+ */
3039
+ progress: (0, import_yup29.object)({
3040
+ collection: (0, import_yup29.string)().optional().nullable().label("Collection"),
3041
+ processed: (0, import_yup29.number)().integer().min(0).default(0).label("Processed"),
3042
+ total: (0, import_yup29.number)().integer().min(0).optional().nullable().label("Total")
3043
+ }).optional().nullable().label("Progress"),
3044
+ counts: (0, import_yup29.object)({
3045
+ countries: GeoSyncCountsSchema.optional().nullable().label("Countries"),
3046
+ states: GeoSyncCountsSchema.optional().nullable().label("States"),
3047
+ cities: GeoSyncCountsSchema.optional().nullable().label("Cities")
3048
+ }).optional().nullable().label("Counts"),
3049
+ /** Why a `skipped` run was skipped, in words the dashboard can show verbatim. */
3050
+ reason: (0, import_yup29.string)().optional().nullable().label("Reason"),
3051
+ /** The failure message on a `failed` run. Surfaced verbatim to the admin. */
3052
+ error: (0, import_yup29.string)().optional().nullable().label("Error")
3053
+ }).label("Geo Sync Run");
3054
+
3055
+ // src/language/language-filter.schema.ts
3056
+ var import_yup30 = require("yup");
3057
+ var LanguageFilterShape = (0, import_yup30.object)({
3058
+ /** Language names, matched exactly. Free-text matching is `?search=`. */
3059
+ names: tokens("Names")
3060
+ }).label("Language Filter");
3061
+ var LanguageFilterSchema = rejectUnknownKeys(
3062
+ LanguageFilterShape,
3063
+ () => Object.keys(LanguageFilterShape.fields)
3064
+ );
3065
+
3066
+ // src/page/page-filter.schema.ts
3067
+ var import_yup31 = require("yup");
3068
+ var PageFilterShape = (0, import_yup31.object)({
3069
+ /**
3070
+ * `type`, NOT `pageType`.
3071
+ *
3072
+ * The enum constant is named `PageType`, which makes `pageType` the obvious
3073
+ * guess, and it is wrong: the stored key is `type` (verified — `pageType`
3074
+ * exists on 0 of 1,449 documents, while `type` splits 1,198 company / 251
3075
+ * institute). The name of a constant is not the name of a field.
3076
+ */
3077
+ type: enumFacet(SupportedPageTypes, "Page Type"),
3078
+ /**
3079
+ * Publication state. Worth knowing when filtering: 69 documents carry NO
3080
+ * `status` at all and some carry `null`, so `status: ['published']` is a real
3081
+ * narrowing (1,379 of 1,449) and absent means "no constraint", not "published".
3082
+ */
3083
+ status: enumFacet(SupportedPageStatuses, "Status"),
3084
+ // ── Flags ───────────────────────────────────────────────────────────────────
3085
+ // No `.default(false)`: absent means no constraint. Note `isFeatured` is
3086
+ // present on only 55 documents, so combining it with `status: ['published']`
3087
+ // leaves 11 pages — a legitimate filter that returns almost nothing.
3088
+ isFeatured: (0, import_yup31.boolean)().optional().label("Featured Only"),
3089
+ /** Lowercase (`in`, `se`, `ww`), like groups. See `GroupFilterSchema`. */
3090
+ featuredMarkets: tokens("Featured Markets"),
3091
+ /** Page SLUGS. A company is matched by slug: a display name breaks on rename. */
3092
+ slugs: tokens("Slugs")
3093
+ }).label("Page Filter");
3094
+ var PAGE_FILTER_KEYS = Object.keys(PageFilterShape.fields);
3095
+ var PageFilterSchema = rejectUnknownKeys(
3096
+ PageFilterShape,
3097
+ () => PAGE_FILTER_KEYS
3098
+ );
3099
+
3100
+ // src/marketing/marketing.schema.ts
3101
+ var import_yup33 = require("yup");
2244
3102
 
2245
3103
  // src/marketing/marketing.constant.ts
2246
3104
  var CampaignStatus = /* @__PURE__ */ ((CampaignStatus2) => {
2247
3105
  CampaignStatus2["Draft"] = "draft";
2248
- CampaignStatus2["Sending"] = "sending";
2249
- CampaignStatus2["Sent"] = "sent";
3106
+ CampaignStatus2["Running"] = "running";
3107
+ CampaignStatus2["Paused"] = "paused";
3108
+ CampaignStatus2["Stopped"] = "stopped";
3109
+ CampaignStatus2["Completed"] = "completed";
2250
3110
  CampaignStatus2["Failed"] = "failed";
2251
3111
  return CampaignStatus2;
2252
3112
  })(CampaignStatus || {});
2253
3113
  var SupportedCampaignStatuses = Object.values(CampaignStatus);
3114
+ var TerminalCampaignStatuses = [
3115
+ "stopped" /* Stopped */,
3116
+ "completed" /* Completed */,
3117
+ "failed" /* Failed */
3118
+ ];
3119
+ var CampaignStatusTransitions = {
3120
+ ["draft" /* Draft */]: ["running" /* Running */],
3121
+ ["running" /* Running */]: [
3122
+ "paused" /* Paused */,
3123
+ "stopped" /* Stopped */,
3124
+ "completed" /* Completed */,
3125
+ "failed" /* Failed */
3126
+ ],
3127
+ ["paused" /* Paused */]: ["running" /* Running */, "stopped" /* Stopped */],
3128
+ ["stopped" /* Stopped */]: [],
3129
+ ["completed" /* Completed */]: [],
3130
+ ["failed" /* Failed */]: []
3131
+ };
3132
+ function canTransitionCampaign(from, to) {
3133
+ return CampaignStatusTransitions[from]?.includes(to) ?? false;
3134
+ }
2254
3135
  var CampaignTriggerKind = /* @__PURE__ */ ((CampaignTriggerKind2) => {
2255
3136
  CampaignTriggerKind2["Manual"] = "manual";
2256
3137
  CampaignTriggerKind2["Once"] = "once";
2257
3138
  CampaignTriggerKind2["Schedule"] = "schedule";
2258
- CampaignTriggerKind2["Lifecycle"] = "lifecycle";
2259
3139
  return CampaignTriggerKind2;
2260
3140
  })(CampaignTriggerKind || {});
2261
3141
  var SupportedCampaignTriggerKinds = Object.values(CampaignTriggerKind);
@@ -2273,21 +3153,16 @@ var CampaignEndKind = /* @__PURE__ */ ((CampaignEndKind2) => {
2273
3153
  return CampaignEndKind2;
2274
3154
  })(CampaignEndKind || {});
2275
3155
  var SupportedCampaignEndKinds = Object.values(CampaignEndKind);
2276
- var CampaignAnchorField = /* @__PURE__ */ ((CampaignAnchorField2) => {
2277
- CampaignAnchorField2["CreatedAt"] = "createdAt";
2278
- CampaignAnchorField2["OnboardingCompletedAt"] = "onboardingCompletedAt";
2279
- CampaignAnchorField2["MarketingConsentUpdatedAt"] = "marketingConsent.updatedAt";
2280
- return CampaignAnchorField2;
2281
- })(CampaignAnchorField || {});
2282
- var SupportedCampaignAnchorFields = Object.values(CampaignAnchorField);
2283
- var BindingKind = /* @__PURE__ */ ((BindingKind2) => {
2284
- BindingKind2["Static"] = "static";
2285
- BindingKind2["Recipient"] = "recipient";
2286
- BindingKind2["Url"] = "url";
2287
- BindingKind2["Source"] = "source";
2288
- return BindingKind2;
2289
- })(BindingKind || {});
2290
- var SupportedBindingKinds = Object.values(BindingKind);
3156
+ var TemplateVariableBindingKind = /* @__PURE__ */ ((TemplateVariableBindingKind2) => {
3157
+ TemplateVariableBindingKind2["Static"] = "static";
3158
+ TemplateVariableBindingKind2["Recipient"] = "recipient";
3159
+ TemplateVariableBindingKind2["Url"] = "url";
3160
+ TemplateVariableBindingKind2["Source"] = "source";
3161
+ return TemplateVariableBindingKind2;
3162
+ })(TemplateVariableBindingKind || {});
3163
+ var SupportedTemplateVariableBindingKinds = Object.values(
3164
+ TemplateVariableBindingKind
3165
+ );
2291
3166
  var OccurrenceStatus = /* @__PURE__ */ ((OccurrenceStatus2) => {
2292
3167
  OccurrenceStatus2["Pending"] = "pending";
2293
3168
  OccurrenceStatus2["Running"] = "running";
@@ -2299,24 +3174,50 @@ var OccurrenceStatus = /* @__PURE__ */ ((OccurrenceStatus2) => {
2299
3174
  var SupportedOccurrenceStatuses = Object.values(OccurrenceStatus);
2300
3175
  var CAMPAIGN_MAX_RECIPIENTS_CEILING = 5e3;
2301
3176
 
2302
- // src/marketing/marketing.schema.ts
2303
- var tokens = (label) => (0, import_yup20.array)().of((0, import_yup20.string)().trim().required()).optional().default([]).label(label);
2304
- var enumFacet = (values, label) => (0, import_yup20.array)().of((0, import_yup20.string)().oneOf(values).required()).optional().default([]).label(label);
2305
- var days = (label) => (0, import_yup20.number)().integer().min(0).max(3650).optional().default(void 0).label(label);
2306
- var months = (label) => (0, import_yup20.number)().integer().min(0).max(1200).optional().default(void 0).label(label);
2307
- var percent = (label) => (0, import_yup20.number)().integer().min(0).max(100).optional().default(void 0).label(label);
2308
- var epoch = (label) => (0, import_yup20.number)().integer().min(0).optional().default(void 0).label(label);
2309
- var salaryBound = (label) => (0, import_yup20.number)().integer().min(0).optional().default(void 0).label(label).when("salaryCurrencies", {
2310
- is: (v) => !Array.isArray(v) || v.length !== 1,
2311
- then: (s) => s.test(
2312
- "requires-single-currency",
2313
- "Pick exactly one salary currency to filter by salary",
2314
- (value) => value === void 0 || value === null
2315
- )
2316
- });
2317
- var ListFilterBranchSchema = (0, import_yup20.object)({
3177
+ // src/user/user-filter.schema.ts
3178
+ var import_yup32 = require("yup");
3179
+ var UserFilterBranchShape = (0, import_yup32.object)({
3180
+ /**
3181
+ * Exact email addresses. An IDENTITY lookup, not a search.
3182
+ *
3183
+ * The admin `search` box already matches email, but as a case-insensitive
3184
+ * unanchored regex over email + first + last, which no index can seek: it
3185
+ * scans every key. Measured against 103,680 users, finding one person by
3186
+ * their full address examined all 103,680 documents and took 552ms, where the
3187
+ * same lookup as an exact match on the indexed `email` field is 0ms and
3188
+ * examines 1.
3189
+ *
3190
+ * So this is for when the caller ALREADY KNOWS the address (previewing what
3191
+ * one named recipient will receive, reconciling a bounce) and `search` is for
3192
+ * when a human is typing a fragment.
3193
+ *
3194
+ * Values are LOWER-CASED before matching, not matched case-insensitively: all
3195
+ * 103,680 stored addresses are already lower case (verified), so normalising
3196
+ * the input keeps the match an indexed equality instead of a regex that would
3197
+ * put the scan straight back.
3198
+ */
3199
+ emails: (0, import_yup32.array)().of((0, import_yup32.string)().required()).optional().default([]).label("Email Addresses"),
3200
+ /**
3201
+ * Better Auth account ids. An IDENTITY lookup like `emails`, and for the same
3202
+ * reason: the caller already knows the exact value.
3203
+ *
3204
+ * BACKEND-ONLY, despite living beside the human-facing facets. The consumer
3205
+ * is hono-auth, which holds an account id and needs the profile's `shortId`
3206
+ * to sign into the JWT it is minting. It cannot use `GET /me` (it is the one
3207
+ * issuing the token) and cannot use `GET /users/:shortId` (the shortId is
3208
+ * precisely what it lacks), so the list route with a facet is the lookup.
3209
+ *
3210
+ * Matched as an indexed equality on `authAccountId`, which is already indexed
3211
+ * as the mirror's link key. No case normalisation, unlike `emails`: these are
3212
+ * opaque generated ids, stored exactly as Better Auth issued them, so folding
3213
+ * case could only ever turn a hit into a miss.
3214
+ *
3215
+ * The admin filter panel builds its controls explicitly rather than from this
3216
+ * shape, so declaring it here does not put an account-id box in the UI.
3217
+ */
3218
+ authAccountIds: (0, import_yup32.array)().of((0, import_yup32.string)().required()).optional().default([]).label("Auth Account IDs"),
2318
3219
  // ── Enum facets (closed vocabularies, chip pickers) ─────────────────────────
2319
- countries: (0, import_yup20.array)().of((0, import_yup20.string)().required()).optional().default([]).label("Countries"),
3220
+ countries: (0, import_yup32.array)().of((0, import_yup32.string)().required()).optional().default([]).label("Countries"),
2320
3221
  roles: enumFacet(SupportedUserRoles, "Roles"),
2321
3222
  // The user's own current seniority (UserSchema.experienceLevel).
2322
3223
  experienceLevels: enumFacet(SupportedExperienceLevels, "Experience Levels"),
@@ -2332,40 +3233,112 @@ var ListFilterBranchSchema = (0, import_yup20.object)({
2332
3233
  SupportedUserProfileVisibilities,
2333
3234
  "Profile Visibility"
2334
3235
  ),
2335
- // Proficiency held on any skill entry (skills[].proficiencyLevel).
2336
- skillProficiencyLevels: enumFacet(
2337
- SupportedProficiencyLevels,
2338
- "Skill Proficiency"
2339
- ),
2340
- // Proficiency held on any language entry (languages[].proficiencyLevel).
2341
- languageProficiencyLevels: enumFacet(
2342
- SupportedProficiencyLevels,
2343
- "Language Proficiency"
2344
- ),
2345
3236
  // Mode of any education entry (educations[].studyType).
3237
+ //
3238
+ // Left as a STANDALONE enum, unlike skill/language proficiency above: it is a
3239
+ // property of a person's education in general ("studied full time"), and the
3240
+ // measured divergence against pairing it with `courses` was 1 user out of
3241
+ // 3,465. Pairing is for qualifiers that are MEANINGLESS alone; this one is
3242
+ // not, so it stays cheap to use.
2346
3243
  studyTypes: enumFacet(SupportedStudyTypes, "Study Type"),
2347
3244
  // Platforms the user linked a social account for (socialAccounts[].type).
2348
3245
  socialAccountTypes: enumFacet(SupportedSocialAccounts, "Social Accounts"),
2349
3246
  // Currency of the user's salary floor (minSalaryCurrency).
2350
3247
  salaryCurrencies: enumFacet(SupportedSalaryCurrencies, "Salary Currency"),
2351
3248
  // ── Free-text token facets (nested profile arrays) ──────────────────────────
2352
- skills: tokens("Skills"),
2353
- languages: tokens("Languages"),
3249
+ // Skill and language each carry their OWN proficiency, because a level is
3250
+ // only meaningful attached to the thing it qualifies. See `qualified`:
3251
+ // as two independent facets, "python + expert" returned 301 users where only
3252
+ // 103 were actually expert at Python.
3253
+ //
3254
+ // `[{ name: 'python', proficiencyLevel: 'expert' }]` — the level is optional,
3255
+ // so naming a skill without one still filters on the skill alone.
3256
+ skills: qualified(SupportedProficiencyLevels, "proficiencyLevel", "Skills"),
3257
+ languages: qualified(SupportedProficiencyLevels, "proficiencyLevel", "Languages"),
2354
3258
  interests: tokens("Interests"),
2355
- companies: tokens("Companies"),
3259
+ // LOOSE forms, kept deliberately. Each of these is a real question on its own
3260
+ // ("worked at Google", "was an engineer somewhere"), and asking for two of
3261
+ // them usually does mean "has each of these somewhere in their history".
3262
+ // They AND across the whole array, so different entries may satisfy each.
3263
+ //
3264
+ // `companyName` / `instituteName`, NOT the `company` widget the job filter
3265
+ // uses. Both are PICKED from real pages — a profile stores the whole
3266
+ // `PageSchema.pick(["name","slug","type","logo"])` object, not free text — but
3267
+ // the facet matches `workExperiences.company.name`, so the value to store is
3268
+ // the page's NAME. Storing its slug would be a picker that matches nothing.
3269
+ companies: tokens("Companies").meta({ widget: "companyName" }),
2356
3270
  designations: tokens("Job Titles / Designations"),
2357
- institutes: tokens("Institutes"),
3271
+ institutes: tokens("Institutes").meta({ widget: "instituteName" }),
2358
3272
  courses: tokens("Courses"),
2359
3273
  fieldsOfStudy: tokens("Fields of Study"),
2360
- certifications: tokens("Certifications"),
2361
- certificationAuthorities: tokens("Certification Authorities"),
3274
+ // PRECISE forms: all parts must describe ONE entry.
3275
+ //
3276
+ // The narrower question the loose facets above cannot express. "Engineer AT
3277
+ // Google" as `companies=Google & designations=engineer` also matches someone
3278
+ // who was an engineer at Acme and a designer at Google, because Mongo
3279
+ // satisfies each path from a different array entry. Measured on production:
3280
+ // VIT + B.Tech returned 71 users where 64 had one education entry that was
3281
+ // both, and Infosys + Test Engineer returned 7 where 6 actually matched.
3282
+ //
3283
+ // Additive rather than a replacement: both readings are legitimate, so the
3284
+ // vocabulary carries both and the caller says which it means. 1,573 users
3285
+ // have two or more work entries and 1,761 have two or more educations, so the
3286
+ // distinction is real and grows as profiles fill in.
3287
+ //
3288
+ // The fields mirror what the profile form actually collects for one entry
3289
+ // (`WorkExperienceSchema` / `EducationSchema`), minus the parts that cannot
3290
+ // narrow an audience: `description` is prose, and a per-entry `location`
3291
+ // duplicates the top-level geography facets against a much emptier path.
3292
+ //
3293
+ // `isCurrent` reads `duration.isActive`, which is what the profile form calls
3294
+ // "I currently work here". It is the field that makes this facet worth having
3295
+ // per entry: "engineer at Google" and "engineer at Google NOW" are different
3296
+ // audiences, and only the second is reachable without it. Live counts:
3297
+ // 2,190 users have a current job against 4,527 with a past one, 1,456 have a
3298
+ // remote entry, 4,088 are still studying.
3299
+ workHistory: conjunction(
3300
+ [
3301
+ "company",
3302
+ "designation",
3303
+ { name: "isCurrent", type: "boolean" },
3304
+ { name: "isRemote", type: "boolean" }
3305
+ ],
3306
+ "Work History"
3307
+ ),
3308
+ educationHistory: conjunction(
3309
+ [
3310
+ "institute",
3311
+ "course",
3312
+ "fieldOfStudy",
3313
+ { name: "studyType", type: "string", oneOf: SupportedStudyTypes },
3314
+ { name: "isCurrent", type: "boolean" },
3315
+ { name: "isDistanceLearning", type: "boolean" }
3316
+ ],
3317
+ "Education History"
3318
+ ),
3319
+ // The issuing body belongs to the certificate, not to the person: "AWS SA"
3320
+ // plus "Microsoft" as two facets matches someone holding an AWS cert AND any
3321
+ // unrelated Microsoft one. 38% of users with a certification hold two or more,
3322
+ // so the entries are there to diverge.
3323
+ //
3324
+ // `authority` is free text rather than a closed vocabulary, hence `"open"`.
3325
+ certifications: qualified("open", "authority", "Certifications"),
2362
3326
  projects: tokens("Projects"),
2363
3327
  jobRoles: tokens("Target Roles"),
2364
3328
  jobLocations: tokens("Preferred Job Locations"),
2365
3329
  // Companies a recruiter is hiring for (recruiterProfile.hiringFor[].page.name).
2366
- hiringForCompanies: tokens("Hiring For (Companies)"),
3330
+ // A picked page (`recruiterProfile.hiringFor.page`), matched on `.name`.
3331
+ //
3332
+ // `pageName`, which searches BOTH page types: `RecruiterPageLinkSchema` picks
3333
+ // `PageSchema` without constraining `type`, so a recruiter may be hiring for
3334
+ // an institute as legitimately as for a company. Production carries exactly
3335
+ // that on the coordinator side — a `coordinatesAt` entry holding a company —
3336
+ // so scoping either of these to one type would hide real data.
3337
+ hiringForCompanies: tokens("Hiring For").meta({ widget: "pageName" }),
2367
3338
  // Institutes a coordinator works at (coordinatorProfile.coordinatesAt[].page.name).
2368
- coordinatesAtInstitutes: tokens("Coordinates At (Institutes)"),
3339
+ // Same: a picked page (`coordinatorProfile.coordinatesAt.page`), on `.name`,
3340
+ // and likewise unconstrained in `type`.
3341
+ coordinatesAtInstitutes: tokens("Coordinates At").meta({ widget: "pageName" }),
2369
3342
  // Case-insensitive keyword match against headline + aboutMe.
2370
3343
  keywords: tokens("Profile Keywords"),
2371
3344
  // ── Range / boolean facets ──────────────────────────────────────────────────
@@ -2373,14 +3346,16 @@ var ListFilterBranchSchema = (0, import_yup20.object)({
2373
3346
  // integer and 50000 SEK is not 50000 USD, so an un-currencied range matches
2374
3347
  // across currencies and quietly means nothing. Requiring exactly one currency
2375
3348
  // alongside a bound is what makes the comparison sound.
2376
- minSalaryFloor: salaryBound("Min Salary \u2265"),
2377
- minSalaryCeil: salaryBound("Min Salary \u2264"),
2378
- openToWorkOnly: (0, import_yup20.boolean)().optional().default(false).label("Open to Work Only"),
2379
- remoteExperienceOnly: (0, import_yup20.boolean)().optional().default(false).label("Has Remote Experience"),
2380
- hasResumeOnly: (0, import_yup20.boolean)().optional().default(false).label("Has Resume"),
2381
- onboardingCompletedOnly: (0, import_yup20.boolean)().optional().default(false).label("Onboarding Completed"),
2382
- emailVerifiedOnly: (0, import_yup20.boolean)().optional().default(false).label("Email Verified Only"),
2383
- mobileVerifiedOnly: (0, import_yup20.boolean)().optional().default(false).label("Mobile Verified Only"),
3349
+ // The sibling currency facet is named explicitly: this bound is inert without
3350
+ // it, so the dependency is stated rather than assumed.
3351
+ minSalaryFloor: currencyBound("Min Salary \u2265", "salaryCurrencies"),
3352
+ minSalaryCeil: currencyBound("Min Salary \u2264", "salaryCurrencies"),
3353
+ openToWorkOnly: (0, import_yup32.boolean)().optional().default(false).label("Open to Work Only"),
3354
+ remoteExperienceOnly: (0, import_yup32.boolean)().optional().default(false).label("Has Remote Experience"),
3355
+ hasResumeOnly: (0, import_yup32.boolean)().optional().default(false).label("Has Resume"),
3356
+ onboardingCompletedOnly: (0, import_yup32.boolean)().optional().default(false).label("Onboarding Completed"),
3357
+ emailVerifiedOnly: (0, import_yup32.boolean)().optional().default(false).label("Email Verified Only"),
3358
+ mobileVerifiedOnly: (0, import_yup32.boolean)().optional().default(false).label("Mobile Verified Only"),
2384
3359
  // ── Lifecycle windows ───────────────────────────────────────────────────────
2385
3360
  // Relative, because a saved list is resolved fresh on every send: a stored
2386
3361
  // absolute epoch would freeze at authoring time and the audience would decay
@@ -2408,10 +3383,10 @@ var ListFilterBranchSchema = (0, import_yup20.object)({
2408
3383
  // Radius search around a point, against location.geo. All three required
2409
3384
  // together; validated as a group rather than field-by-field so a half-filled
2410
3385
  // radius cannot silently match everyone.
2411
- withinRadius: (0, import_yup20.object)({
2412
- lat: (0, import_yup20.number)().min(-90).max(90).required().label("Latitude"),
2413
- lng: (0, import_yup20.number)().min(-180).max(180).required().label("Longitude"),
2414
- km: (0, import_yup20.number)().moreThan(0).max(2e4).required().label("Radius (km)")
3386
+ withinRadius: (0, import_yup32.object)({
3387
+ lat: (0, import_yup32.number)().min(-90).max(90).required().label("Latitude"),
3388
+ lng: (0, import_yup32.number)().min(-180).max(180).required().label("Longitude"),
3389
+ km: (0, import_yup32.number)().moreThan(0).max(2e4).required().label("Radius (km)")
2415
3390
  }).optional().default(void 0).label("Within Radius"),
2416
3391
  // ── Signup context (immutable market / locale) ──────────────────────────────
2417
3392
  signupCountries: tokens("Signup Countries"),
@@ -2422,14 +3397,27 @@ var ListFilterBranchSchema = (0, import_yup20.object)({
2422
3397
  // match — filtering on this necessarily excludes them.
2423
3398
  educationLevels: enumFacet(SupportedEducationLevels, "Education Level"),
2424
3399
  // ── Consent metadata ────────────────────────────────────────────────────────
2425
- // `marketingConsent.subscribed` is NOT a facet: it is a hard opt-in gate
2426
- // applied unconditionally by user-service and is not relaxable from here.
3400
+ // `marketingSubscribed` IS a facet, and deliberately so. It used to be a hard
3401
+ // opt-in gate applied unconditionally by user-service; that gate was removed
3402
+ // when user-service became a pure data source, so consent is now an ordinary
3403
+ // filter that marketing-service defaults to `true` in `resolveAudience`.
3404
+ //
3405
+ // NO `.default()` HERE, unlike every other boolean facet. The send spreads its
3406
+ // default first and lets a stored value win, so this must distinguish "the
3407
+ // list said nothing" (absent -> default applies) from "the list said false"
3408
+ // (override -> mails users who never opted in). A `.default(false)` would
3409
+ // materialise the second case on every list that never chose it.
3410
+ //
3411
+ // @todo CONSENT-FILTER-ENFORCEMENT — nothing enforces consent anywhere now.
3412
+ marketingSubscribed: (0, import_yup32.boolean)().optional().label("Marketing Subscribed"),
2427
3413
  jobAlertFrequencies: enumFacet(
2428
3414
  SupportedJobAlertFrequencies,
2429
3415
  "Job Alert Frequency"
2430
3416
  ),
2431
3417
  consentSources: tokens("Consent Source"),
2432
3418
  consentUpdatedWithinDays: days("Consent Updated Within (days)"),
3419
+ /** The absolute bound `consentUpdatedWithinDays` resolves into (epoch ms). */
3420
+ consentUpdatedAfter: epoch("Consent Updated After"),
2433
3421
  // ── Notification preferences ────────────────────────────────────────────────
2434
3422
  // Read as opt-IN (`=== true`), matching marketingConsent and NOT the `!== false`
2435
3423
  // reading used for transactional notification routing. A campaign is bulk mail,
@@ -2437,19 +3425,31 @@ var ListFilterBranchSchema = (0, import_yup20.object)({
2437
3425
  // `productUpdates` defaults to false on the user document, so targeting it
2438
3426
  // selects only people who deliberately turned it on.
2439
3427
  notificationChannelsEnabled: enumFacet(
2440
- SupportedNotificationChannels,
3428
+ SupportedNotificationTypes,
2441
3429
  "Notification Channels Enabled"
2442
3430
  ),
2443
3431
  // ── Tenure / recency ────────────────────────────────────────────────────────
2444
3432
  // Against workExperiences[].duration and educations[].duration ("YYYY-MM").
2445
- currentlyEmployed: (0, import_yup20.boolean)().optional().default(false).label("Currently Employed"),
3433
+ currentlyEmployed: (0, import_yup32.boolean)().optional().default(false).label("Currently Employed"),
2446
3434
  graduatedWithinMonths: months("Graduated Within (months)"),
2447
3435
  skillUsedWithinMonths: months("Skill Used Within (months)"),
3436
+ // The ABSOLUTE bounds the three relative windows above resolve into
3437
+ // (`graduatedWithinMonths` -> `graduatedAfter`, and so on). Both forms are
3438
+ // accepted: a caller may send either, and user-service derives the bound when
3439
+ // only the window is given.
3440
+ //
3441
+ // A UI should offer the WINDOW, not these. A saved list is re-resolved on
3442
+ // every send, so a stored absolute date rots — "graduated after 2024-03" keeps
3443
+ // meaning that forever, while "within 18 months" keeps meaning what the admin
3444
+ // intended. They are declared because the vocabulary must be complete, not
3445
+ // because a list should store one.
3446
+ graduatedAfter: (0, import_yup32.string)().trim().optional().label("Graduated After"),
3447
+ skillUsedAfter: (0, import_yup32.string)().trim().optional().label("Skill Used After"),
2448
3448
  // ── Profile richness ────────────────────────────────────────────────────────
2449
- hasImage: (0, import_yup20.boolean)().optional().default(false).label("Has Photo"),
2450
- hasHeadline: (0, import_yup20.boolean)().optional().default(false).label("Has Headline"),
2451
- hasAboutMe: (0, import_yup20.boolean)().optional().default(false).label("Has About Me"),
2452
- hasMobile: (0, import_yup20.boolean)().optional().default(false).label("Has Mobile Number"),
3449
+ hasImage: (0, import_yup32.boolean)().optional().default(false).label("Has Photo"),
3450
+ hasHeadline: (0, import_yup32.boolean)().optional().default(false).label("Has Headline"),
3451
+ hasAboutMe: (0, import_yup32.boolean)().optional().default(false).label("Has About Me"),
3452
+ hasMobile: (0, import_yup32.boolean)().optional().default(false).label("Has Mobile Number"),
2453
3453
  // Segment by WHICH profile section is missing (profileCompleteness.missing[]),
2454
3454
  // rather than only by the overall percentage.
2455
3455
  missingSections: tokens("Missing Profile Sections"),
@@ -2461,50 +3461,39 @@ var ListFilterBranchSchema = (0, import_yup20.object)({
2461
3461
  // data lives in other services' collections. Tri-state: `false` is a real
2462
3462
  // filter ("has NO job alert"), which is the audience an alert nudge wants, so
2463
3463
  // these are `undefined` when unset rather than defaulting to false.
2464
- hasJobAlert: (0, import_yup20.boolean)().optional().default(void 0).label("Has a Job Alert"),
2465
- hasApplied: (0, import_yup20.boolean)().optional().default(void 0).label("Has Applied"),
2466
- appliedWithinDays: days("Applied Within (days)"),
2467
- // ── Free-text search (name / email) ─────────────────────────────────────────
2468
- search: (0, import_yup20.string)().trim().optional().label("Search")
3464
+ hasJobAlert: (0, import_yup32.boolean)().optional().default(void 0).label("Has a Job Alert"),
3465
+ hasApplied: (0, import_yup32.boolean)().optional().default(void 0).label("Has Applied"),
3466
+ appliedWithinDays: days("Applied Within (days)")
3467
+ // `search` is NOT here. It is free text over name/email, not a facet: it names
3468
+ // no field on a user document, user-service treats it as a RESERVED PARAM
3469
+ // beside the filter rather than a filter key, and it is meaningless inside an
3470
+ // `or` branch (a union of "matches this text" with a facet clause). It travels
3471
+ // as a sibling of the filter — see `PreviewFilterSchema` in marketing-service —
3472
+ // so a saved audience cannot carry free text it would silently re-run later.
2469
3473
  }).label("Audience Filter Branch");
2470
- var rejectUnknownKeys = (schema, allowed) => schema.test(
2471
- "no-unknown-filter-keys",
2472
- ({ value, label }) => {
2473
- const known = new Set(allowed());
2474
- const bad = Object.keys(value ?? {}).filter(
2475
- (k) => !known.has(k)
2476
- );
2477
- return `${label} has unsupported keys: ${bad.join(", ")}`;
2478
- },
2479
- (value) => {
2480
- if (value == null) return true;
2481
- const known = new Set(allowed());
2482
- return Object.keys(value).every(
2483
- (k) => known.has(k)
2484
- );
2485
- }
2486
- );
2487
- var BRANCH_KEYS = Object.keys(ListFilterBranchSchema.fields);
2488
- var ListFilterBranch = rejectUnknownKeys(
2489
- ListFilterBranchSchema,
2490
- () => BRANCH_KEYS
2491
- );
2492
- var ListFilterSchema = rejectUnknownKeys(
2493
- ListFilterBranchSchema.shape({
2494
- or: (0, import_yup20.array)().of(ListFilterBranch).optional().default([]).label("Or Any Of")
2495
- }).label("Audience Filter"),
3474
+ var BRANCH_KEYS = Object.keys(UserFilterBranchShape.fields);
3475
+ var UserFilterBranch = rejectUnknownKeys(UserFilterBranchShape, () => BRANCH_KEYS);
3476
+ var UserFilterSchema = rejectUnknownKeys(
3477
+ UserFilterBranchShape.shape({
3478
+ or: (0, import_yup32.array)().of(UserFilterBranch).optional().default([]).label("Or Any Of")
3479
+ }).label("User Filter"),
2496
3480
  () => [...BRANCH_KEYS, "or"]
2497
3481
  );
2498
- var MailingListSchema = (0, import_yup20.object)({
2499
- name: (0, import_yup20.string)().required().min(1).max(120).label("Name"),
2500
- description: (0, import_yup20.string)().optional().max(500).label("Description"),
2501
- filter: ListFilterSchema.default(() => ListFilterSchema.getDefault())
3482
+
3483
+ // src/marketing/marketing.schema.ts
3484
+ var MailingListFilterSchema = UserFilterSchema;
3485
+ var MailingListSchema = (0, import_yup33.object)({
3486
+ name: (0, import_yup33.string)().required().min(1).max(120).label("Name"),
3487
+ description: (0, import_yup33.string)().optional().max(500).label("Description"),
3488
+ filter: MailingListFilterSchema.default(
3489
+ () => MailingListFilterSchema.getDefault()
3490
+ )
2502
3491
  }).label("Mailing List");
2503
- var CampaignTriggerSchema = (0, import_yup20.object)({
2504
- kind: (0, import_yup20.string)().oneOf(SupportedCampaignTriggerKinds).required().default("manual" /* Manual */).label("Trigger"),
3492
+ var CampaignTriggerSchema = (0, import_yup33.object)({
3493
+ kind: (0, import_yup33.string)().oneOf(SupportedCampaignTriggerKinds).required().default("manual" /* Manual */).label("Trigger"),
2505
3494
  // IANA zone, e.g. "Asia/Kolkata". Local wall-clock times are interpreted in it,
2506
3495
  // so occurrences land at the intended hour across DST rather than drifting.
2507
- timezone: (0, import_yup20.string)().optional().default("UTC").label("Timezone").when("kind", {
3496
+ timezone: (0, import_yup33.string)().optional().default("UTC").label("Timezone").when("kind", {
2508
3497
  is: (k) => k !== "manual" /* Manual */,
2509
3498
  then: (s) => s.required()
2510
3499
  }),
@@ -2518,26 +3507,26 @@ var CampaignTriggerSchema = (0, import_yup20.object)({
2518
3507
  is: "schedule" /* Schedule */,
2519
3508
  then: (s) => s.required()
2520
3509
  }),
2521
- interval: (0, import_yup20.object)({
2522
- every: (0, import_yup20.number)().integer().min(1).max(365).required().label("Every"),
2523
- unit: (0, import_yup20.string)().oneOf(SupportedCampaignIntervalUnits).required().label("Unit")
3510
+ interval: (0, import_yup33.object)({
3511
+ every: (0, import_yup33.number)().integer().min(1).max(365).required().label("Every"),
3512
+ unit: (0, import_yup33.string)().oneOf(SupportedCampaignIntervalUnits).required().label("Unit")
2524
3513
  }).optional().default(void 0).label("Repeats").when("kind", {
2525
3514
  is: "schedule" /* Schedule */,
2526
3515
  then: (s) => s.required()
2527
3516
  }),
2528
3517
  /** Local wall-clock send time, "HH:mm" in `timezone`. */
2529
- timeOfDay: (0, import_yup20.string)().matches(/^([01]\d|2[0-3]):[0-5]\d$/, "Use HH:mm").optional().default("09:00").label("Time of Day"),
3518
+ timeOfDay: (0, import_yup33.string)().matches(/^([01]\d|2[0-3]):[0-5]\d$/, "Use HH:mm").optional().default("09:00").label("Time of Day"),
2530
3519
  /** For weekly intervals: ISO weekdays, 1 = Monday .. 7 = Sunday. */
2531
- byWeekday: (0, import_yup20.array)().of((0, import_yup20.number)().integer().min(1).max(7).required()).optional().default([]).label("On Days"),
3520
+ byWeekday: (0, import_yup33.array)().of((0, import_yup33.number)().integer().min(1).max(7).required()).optional().default([]).label("On Days"),
2532
3521
  /**
2533
3522
  * For monthly intervals: 1-31, or -1 meaning "last day of the month". A month
2534
3523
  * shorter than the chosen day is clamped to its last day, so 31 in February
2535
3524
  * fires on the 28th/29th rather than being skipped.
2536
3525
  */
2537
- byMonthday: (0, import_yup20.number)().integer().min(-1).max(31).notOneOf([0]).optional().default(void 0).label("Day of Month"),
2538
- end: (0, import_yup20.object)({
2539
- kind: (0, import_yup20.string)().oneOf(SupportedCampaignEndKinds).required().label("Ends"),
2540
- occurrences: (0, import_yup20.number)().integer().min(1).max(1e4).optional().default(void 0).label("After N Sends").when("kind", {
3526
+ byMonthday: (0, import_yup33.number)().integer().min(-1).max(31).notOneOf([0]).optional().default(void 0).label("Day of Month"),
3527
+ end: (0, import_yup33.object)({
3528
+ kind: (0, import_yup33.string)().oneOf(SupportedCampaignEndKinds).required().label("Ends"),
3529
+ occurrences: (0, import_yup33.number)().integer().min(1).max(1e4).optional().default(void 0).label("After N Sends").when("kind", {
2541
3530
  is: "afterOccurrences" /* AfterOccurrences */,
2542
3531
  then: (s) => s.required()
2543
3532
  }),
@@ -2545,52 +3534,80 @@ var CampaignTriggerSchema = (0, import_yup20.object)({
2545
3534
  is: "onDate" /* OnDate */,
2546
3535
  then: (s) => s.required()
2547
3536
  })
2548
- }).optional().default(() => ({ kind: "never" /* Never */ })).label("End Condition"),
2549
- // kind: lifecycle
2550
- anchorField: (0, import_yup20.string)().oneOf(SupportedCampaignAnchorFields).optional().default(void 0).label("Anchor Date").when("kind", {
2551
- is: "lifecycle" /* Lifecycle */,
2552
- then: (s) => s.required()
2553
- }),
2554
- /** Days after the anchor at which this user becomes eligible. */
2555
- offsetDays: (0, import_yup20.number)().integer().min(0).max(3650).optional().default(void 0).label("Days After Anchor").when("kind", {
2556
- is: "lifecycle" /* Lifecycle */,
2557
- then: (s) => s.required()
2558
- }),
3537
+ }).optional().default(() => ({ kind: "never" /* Never */ })).label("End Condition")
3538
+ }).label("Trigger");
3539
+ var CampaignDataSourceSchema = (0, import_yup33.object)({
3540
+ id: (0, import_yup33.string)().required().label("Data Source"),
2559
3541
  /**
2560
- * How long a user stays eligible after crossing the offset. Needed because the
2561
- * scheduler samples periodically and a zero-width window would miss anyone not
2562
- * observed on the exact day. A user inside the window on several ticks is still
2563
- * mailed once: the permanent idempotency key, not this value, guarantees that.
3542
+ * Derive this source's filter per recipient from their profile, instead of
3543
+ * using the admin-entered `params` for everyone.
3544
+ *
3545
+ * A second AXIS on a datasource, not a datasource of its own: a
3546
+ * personalisation filter is an ordinary filter for that source, the only
3547
+ * difference being who wrote it. Jobs previously shipped the two modes as
3548
+ * two registry entries (`recent-jobs` / `matched-jobs`), which let them drift
3549
+ * apart down to their output names.
3550
+ *
3551
+ * Optional, and absent reads as false, so every existing campaign stays
3552
+ * un-personalised. Whether a source can honour it at all is declared by the
3553
+ * source itself (`supportsPersonalisation`), which this package cannot see.
2564
3554
  */
2565
- windowDays: (0, import_yup20.number)().integer().min(1).max(90).optional().default(2).label("Eligibility Window (days)")
2566
- }).label("Trigger");
2567
- var BindingSchema = (0, import_yup20.object)({
2568
- kind: (0, import_yup20.string)().oneOf(SupportedBindingKinds).required().label("Source"),
2569
- value: (0, import_yup20.string)().required().min(1).max(2e3).label("Value")
2570
- }).label("Binding");
2571
- var CampaignSchema = (0, import_yup20.object)({
2572
- name: (0, import_yup20.string)().required().min(1).max(200).label("Campaign Name"),
3555
+ personalised: (0, import_yup33.boolean)().optional().label("Personalised"),
3556
+ /**
3557
+ * A free-form bag whose keys are defined by the chosen source's OWN
3558
+ * `paramsSchema`, which this package cannot see. Same `lazy` treatment as
3559
+ * `bindings` above, and for the same reason: a bare `object()` has no known
3560
+ * keys, so under `stripUnknown: true` every param is "unknown" and the bag
3561
+ * arrives as `{}` — a campaign configured to mail remote Python roles in
3562
+ * Bangalore silently saves as an unfiltered platform-wide digest.
3563
+ *
3564
+ * Values stay unvalidated here on purpose; the marketing service validates
3565
+ * them against the real source schema in `checkCampaignConfig`, which is the
3566
+ * only place that knows what a given source accepts.
3567
+ */
3568
+ params: (0, import_yup33.lazy)((value) => {
3569
+ const keys = value && typeof value === "object" ? Object.keys(value) : [];
3570
+ return (0, import_yup33.object)(
3571
+ Object.fromEntries(keys.map((k) => [k, (0, import_yup33.mixed)().optional()]))
3572
+ ).optional().default(() => ({})).label("Parameters");
3573
+ })
3574
+ }).label("Data Source");
3575
+ var TemplateVariableBindingSchema = (0, import_yup33.object)({
3576
+ kind: (0, import_yup33.string)().oneOf(SupportedTemplateVariableBindingKinds).required().label("Source"),
3577
+ value: (0, import_yup33.string)().required().min(1).max(2e3).label("Value")
3578
+ }).label("Template Variable Binding");
3579
+ var CampaignSchema = (0, import_yup33.object)({
3580
+ name: (0, import_yup33.string)().required().min(1).max(200).label("Campaign Name"),
2573
3581
  // The template renders its own subject; an override is only needed for the
2574
3582
  // inline-HTML path, hence not required when a template is chosen.
2575
- subject: (0, import_yup20.string)().optional().max(255).label("Subject").when("templateKey", {
3583
+ subject: (0, import_yup33.string)().optional().max(255).label("Subject").when("templateKey", {
2576
3584
  is: (v) => !v,
2577
3585
  then: (s) => s.required().min(1)
2578
3586
  }),
2579
3587
  /**
2580
3588
  * Inline HTML body; may contain {{unsubscribe_url}}. Optional now that a
2581
3589
  * campaign can instead name a registry template — exactly one of the two is
2582
- * required. Scheduled and lifecycle campaigns must use a template: the
2583
- * registry path is the one with a compliance-checked footer and a render test,
3590
+ * required. Scheduled campaigns must use a template: the registry path is the
3591
+ * one with a compliance-checked footer and a render test,
2584
3592
  * and hand-pasted HTML that repeats forever is a mistake that repeats forever.
2585
3593
  */
2586
- html: (0, import_yup20.string)().optional().label("HTML Body").when("templateKey", {
3594
+ html: (0, import_yup33.string)().optional().label("HTML Body").when("templateKey", {
2587
3595
  is: (v) => !v,
2588
3596
  then: (s) => s.required().min(1)
2589
3597
  }),
2590
- text: (0, import_yup20.string)().optional().label("Plain-text Body"),
2591
- listId: (0, import_yup20.string)().required().label("Mailing List"),
3598
+ text: (0, import_yup33.string)().optional().label("Plain-text Body"),
3599
+ listId: (0, import_yup33.string)().required().label("Mailing List"),
3600
+ /**
3601
+ * Where the campaign is in its life. See `CampaignStatus` for the transitions.
3602
+ *
3603
+ * Declared here so the enum is ENFORCED on write. It was previously written
3604
+ * straight onto the document by the service with no schema behind it, and a
3605
+ * value the enum never contained (`scheduled`) reached production rows and
3606
+ * rendered in the admin list as though it were real.
3607
+ */
3608
+ status: (0, import_yup33.string)().oneOf(SupportedCampaignStatuses).optional().default("draft" /* Draft */).label("Status"),
2592
3609
  /** Key from email-service's template registry. Mutually exclusive with `html`. */
2593
- templateKey: (0, import_yup20.string)().optional().label("Email Template"),
3610
+ templateKey: (0, import_yup33.string)().optional().label("Email Template"),
2594
3611
  /**
2595
3612
  * One entry per variable the chosen template declares. Validated at save time
2596
3613
  * against the template's own `variables`, so a missing required binding is a
@@ -2605,58 +3622,52 @@ var CampaignSchema = (0, import_yup20.object)({
2605
3622
  * whose required variables all appear unbound. Shaping each entry also means a
2606
3623
  * malformed binding is rejected here rather than at send time.
2607
3624
  */
2608
- bindings: (0, import_yup20.lazy)((value) => {
3625
+ bindings: (0, import_yup33.lazy)((value) => {
2609
3626
  const keys = value && typeof value === "object" ? Object.keys(value) : [];
2610
- return (0, import_yup20.object)(
2611
- Object.fromEntries(keys.map((k) => [k, BindingSchema.required()]))
3627
+ return (0, import_yup33.object)(
3628
+ Object.fromEntries(keys.map((k) => [k, TemplateVariableBindingSchema.required()]))
2612
3629
  ).optional().default(() => ({})).label("Template Data");
2613
3630
  }),
2614
- /** Registered data source supplying per-recipient content, plus its params. */
2615
- dataSource: (0, import_yup20.object)({
2616
- id: (0, import_yup20.string)().required().label("Data Source"),
2617
- /**
2618
- * A free-form bag whose keys are defined by the chosen source's OWN
2619
- * `paramsSchema`, which this package cannot see. Same `lazy` treatment as
2620
- * `bindings` above, and for the same reason: a bare `object()` has no known
2621
- * keys, so under `stripUnknown: true` every param is "unknown" and the bag
2622
- * arrives as `{}` — a campaign configured to mail remote Python roles in
2623
- * Bangalore silently saves as an unfiltered platform-wide digest.
2624
- *
2625
- * Values stay unvalidated here on purpose; the marketing service validates
2626
- * them against the real source schema in `checkCampaignConfig`, which is the
2627
- * only place that knows what a given source accepts.
2628
- */
2629
- params: (0, import_yup20.lazy)((value) => {
2630
- const keys = value && typeof value === "object" ? Object.keys(value) : [];
2631
- return (0, import_yup20.object)(
2632
- Object.fromEntries(keys.map((k) => [k, (0, import_yup20.mixed)().optional()]))
2633
- ).optional().default(() => ({})).label("Parameters");
2634
- })
2635
- }).optional().default(void 0).label("Data Source"),
3631
+ /**
3632
+ * The data sources this campaign draws content from.
3633
+ *
3634
+ * SEVERAL, because one mail may combine them. Each source's outputs are
3635
+ * NAMESPACED by its id in the rendered payload (`jobs.jobCount`,
3636
+ * `groups.groupCount`), so two sources providing the same name cannot
3637
+ * overwrite each other and a template says which one it means.
3638
+ */
3639
+ dataSources: (0, import_yup33.array)().of(CampaignDataSourceSchema).optional().default(void 0).label("Data Sources"),
2636
3640
  trigger: CampaignTriggerSchema.default(
2637
3641
  () => CampaignTriggerSchema.getDefault()
2638
3642
  ),
2639
3643
  // ── Safety ──────────────────────────────────────────────────────────────────
2640
- // Both default to the safe position: a new campaign neither runs nor sends
2641
- // until someone deliberately says so, and says so twice.
2642
- enabled: (0, import_yup20.boolean)().optional().default(false).label("Enabled"),
2643
- dryRun: (0, import_yup20.boolean)().optional().default(true).label("Dry Run"),
2644
- maxRecipients: (0, import_yup20.number)().integer().min(1).max(CAMPAIGN_MAX_RECIPIENTS_CEILING).optional().default(void 0).label("Max Recipients"),
2645
- utm: (0, import_yup20.object)({
2646
- campaign: (0, import_yup20.string)().optional().max(120).label("utm_campaign"),
2647
- content: (0, import_yup20.string)().optional().max(120).label("utm_content")
3644
+ /**
3645
+ * Still TWO deliberate acts before mail leaves, as before: a campaign must be
3646
+ * activated (`status: running`) AND taken out of dry run.
3647
+ *
3648
+ * The first half used to be a separate `enabled` boolean living alongside the
3649
+ * status, which meant two fields encoding one idea and able to disagree — an
3650
+ * `enabled: false` campaign whose status said it was sending was
3651
+ * representable, and the admin list showed exactly that. Activation is a
3652
+ * state transition, so it belongs in the state.
3653
+ */
3654
+ dryRun: (0, import_yup33.boolean)().optional().default(true).label("Dry Run"),
3655
+ maxRecipients: (0, import_yup33.number)().integer().min(1).max(CAMPAIGN_MAX_RECIPIENTS_CEILING).optional().default(void 0).label("Max Recipients"),
3656
+ utm: (0, import_yup33.object)({
3657
+ campaign: (0, import_yup33.string)().optional().max(120).label("utm_campaign"),
3658
+ content: (0, import_yup33.string)().optional().max(120).label("utm_content")
2648
3659
  }).optional().default(void 0).label("Link Tagging")
2649
3660
  }).label("Campaign");
2650
3661
 
2651
3662
  // src/pagination/pagination.schema.ts
2652
- var import_yup21 = require("yup");
2653
- var PaginationSchema = (0, import_yup21.object)().shape({
2654
- page: (0, import_yup21.number)().required().min(1).label("Page").default(1),
2655
- limit: (0, import_yup21.number)().required().min(1).max(100).label("Limit").default(10),
2656
- sortBy: (0, import_yup21.string)().notOneOf([""]).optional().label("SortBy"),
2657
- nextPage: (0, import_yup21.string)().optional().label("Next Page Token"),
2658
- prevPage: (0, import_yup21.string)().optional().label("Previous Page Token"),
2659
- sortDirection: (0, import_yup21.string)().oneOf(["asc", "desc"]).optional().label("Sort Direction")
3663
+ var import_yup34 = require("yup");
3664
+ var PaginationSchema = (0, import_yup34.object)().shape({
3665
+ page: (0, import_yup34.number)().required().min(1).label("Page").default(1),
3666
+ limit: (0, import_yup34.number)().required().min(1).max(100).label("Limit").default(10),
3667
+ sortBy: (0, import_yup34.string)().notOneOf([""]).optional().label("SortBy"),
3668
+ nextPage: (0, import_yup34.string)().optional().label("Next Page Token"),
3669
+ prevPage: (0, import_yup34.string)().optional().label("Previous Page Token"),
3670
+ sortDirection: (0, import_yup34.string)().oneOf(["asc", "desc"]).optional().label("Sort Direction")
2660
3671
  });
2661
3672
 
2662
3673
  // src/pagination/pagination.constant.ts
@@ -2672,7 +3683,7 @@ var DefaultPaginationOptions = {
2672
3683
  };
2673
3684
 
2674
3685
  // src/saved-search/saved-search.schema.ts
2675
- var import_yup22 = require("yup");
3686
+ var import_yup35 = require("yup");
2676
3687
 
2677
3688
  // src/saved-search/saved-search.constant.ts
2678
3689
  var SavedSearchStatus = /* @__PURE__ */ ((SavedSearchStatus2) => {
@@ -2683,21 +3694,21 @@ var SavedSearchStatus = /* @__PURE__ */ ((SavedSearchStatus2) => {
2683
3694
  var SupportedSavedSearchStatuses = Object.values(SavedSearchStatus);
2684
3695
 
2685
3696
  // src/saved-search/saved-search.schema.ts
2686
- var SavedSearchSchema = (0, import_yup22.object)({
3697
+ var SavedSearchSchema = (0, import_yup35.object)({
2687
3698
  /** Public identifier. Server-assigned. */
2688
- shortId: (0, import_yup22.string)().trim().label("Short Id"),
3699
+ shortId: (0, import_yup35.string)().trim().label("Short Id"),
2689
3700
  /**
2690
3701
  * Owner's Better Auth account id. Server-assigned from the verified token,
2691
3702
  * never accepted from a request body.
2692
3703
  */
2693
- ownerUserId: (0, import_yup22.string)().trim().label("Owner"),
3704
+ ownerUserId: (0, import_yup35.string)().trim().label("Owner"),
2694
3705
  /** What the user calls this search, e.g. "Remote React roles". */
2695
- label: (0, import_yup22.string)().trim().max(120).required().label("Label"),
3706
+ label: (0, import_yup35.string)().trim().max(120).required().label("Label"),
2696
3707
  /**
2697
3708
  * The free-text query. Optional because a search can be filters-only ("all
2698
3709
  * remote jobs in Sweden"), which is a legitimate alert to want.
2699
3710
  */
2700
- search: (0, import_yup22.string)().trim().max(500).optional().label("Search Text"),
3711
+ search: (0, import_yup35.string)().trim().max(500).optional().label("Search Text"),
2701
3712
  /**
2702
3713
  * Field filters, in the same `{ field: values }` shape the search endpoint
2703
3714
  * parses from `filter[field]=a,b`. Held as a free-form object rather than a
@@ -2705,7 +3716,7 @@ var SavedSearchSchema = (0, import_yup22.object)({
2705
3716
  * change independently; validating them here would put this package in the
2706
3717
  * business of tracking that vocabulary.
2707
3718
  */
2708
- filter: (0, import_yup22.object)().optional().default({}).label("Filters"),
3719
+ filter: (0, import_yup35.object)().optional().default({}).label("Filters"),
2709
3720
  /**
2710
3721
  * How often to mail matches. `off` keeps the search saved but silent, which is
2711
3722
  * the difference between "I don't want mail" and "delete my search".
@@ -2714,26 +3725,26 @@ var SavedSearchSchema = (0, import_yup22.object)({
2714
3725
  * than defining a second one: two vocabularies for the same concept is how
2715
3726
  * they drift.
2716
3727
  */
2717
- frequency: (0, import_yup22.string)().oneOf(SupportedJobAlertFrequencies).default("off" /* Off */).label("Alert Frequency"),
2718
- status: (0, import_yup22.string)().oneOf(SupportedSavedSearchStatuses).default("active" /* Active */).label("Status"),
3728
+ frequency: (0, import_yup35.string)().oneOf(SupportedJobAlertFrequencies).default("off" /* Off */).label("Alert Frequency"),
3729
+ status: (0, import_yup35.string)().oneOf(SupportedSavedSearchStatuses).default("active" /* Active */).label("Status"),
2719
3730
  /**
2720
3731
  * Epoch ms of the last alert sent for this search. The matching sweep reads it
2721
3732
  * to decide whether the cadence is due, and writes it only AFTER a successful
2722
3733
  * publish, so a failed run retries next tick rather than silently skipping a
2723
3734
  * period.
2724
3735
  */
2725
- lastNotifiedAt: (0, import_yup22.number)().optional().label("Last Notified At"),
3736
+ lastNotifiedAt: (0, import_yup35.number)().optional().label("Last Notified At"),
2726
3737
  /**
2727
3738
  * Epoch ms high-water mark: only jobs created after this are "new" for the
2728
3739
  * next alert. Kept separate from `lastNotifiedAt` because a run that finds no
2729
3740
  * matches should still advance what counts as new, without implying mail was
2730
3741
  * sent.
2731
3742
  */
2732
- lastMatchedAt: (0, import_yup22.number)().optional().label("Last Matched At")
3743
+ lastMatchedAt: (0, import_yup35.number)().optional().label("Last Matched At")
2733
3744
  }).noUnknown().strict();
2734
3745
 
2735
3746
  // src/report/report.schema.ts
2736
- var import_yup23 = require("yup");
3747
+ var import_yup36 = require("yup");
2737
3748
 
2738
3749
  // src/report/report.constant.ts
2739
3750
  var ResourceType = /* @__PURE__ */ ((ResourceType3) => {
@@ -2757,15 +3768,15 @@ var ReportReason = /* @__PURE__ */ ((ReportReason3) => {
2757
3768
  var SupportedReportReasons = Object.values(ReportReason);
2758
3769
 
2759
3770
  // src/report/report.schema.ts
2760
- var ReportSchema = (0, import_yup23.object)({
2761
- type: (0, import_yup23.mixed)().oneOf(SupportedResourceTypes).required().label("Type"),
2762
- resourceId: (0, import_yup23.string)().required().label("Resource ID"),
2763
- reason: (0, import_yup23.mixed)().oneOf(SupportedReportReasons).required("Please choose a reason").label("Reason"),
2764
- comment: (0, import_yup23.string)().max(1e3).optional().label("Comment")
3771
+ var ReportSchema = (0, import_yup36.object)({
3772
+ type: (0, import_yup36.mixed)().oneOf(SupportedResourceTypes).required().label("Type"),
3773
+ resourceId: (0, import_yup36.string)().required().label("Resource ID"),
3774
+ reason: (0, import_yup36.mixed)().oneOf(SupportedReportReasons).required("Please choose a reason").label("Reason"),
3775
+ comment: (0, import_yup36.string)().max(1e3).optional().label("Comment")
2765
3776
  }).label("Report Schema");
2766
3777
 
2767
- // src/feedback/feedback.schema.ts
2768
- var import_yup24 = require("yup");
3778
+ // src/feedback/feedback-filter.schema.ts
3779
+ var import_yup37 = require("yup");
2769
3780
 
2770
3781
  // src/feedback/feedback.constant.ts
2771
3782
  var FeedbackType = /* @__PURE__ */ ((FeedbackType3) => {
@@ -2787,64 +3798,317 @@ var FeedbackStatus = /* @__PURE__ */ ((FeedbackStatus3) => {
2787
3798
  var SupportedFeedbackStatuses = Object.values(FeedbackStatus);
2788
3799
  var FEEDBACK_MESSAGE_MAX = 4e3;
2789
3800
 
3801
+ // src/feedback/feedback-filter.schema.ts
3802
+ var FeedbackFilterShape = (0, import_yup37.object)({
3803
+ type: enumFacet(SupportedFeedbackTypes, "Type"),
3804
+ status: enumFacet(SupportedFeedbackStatuses, "Status")
3805
+ }).label("Feedback Filter");
3806
+ var FeedbackFilterSchema = rejectUnknownKeys(
3807
+ FeedbackFilterShape,
3808
+ () => Object.keys(FeedbackFilterShape.fields)
3809
+ );
3810
+
2790
3811
  // src/feedback/feedback.schema.ts
2791
- var FeedbackSchema = (0, import_yup24.object)({
2792
- type: (0, import_yup24.mixed)().oneOf(SupportedFeedbackTypes).required().label("Type"),
2793
- message: (0, import_yup24.string)().trim().required("Please enter a message").max(FEEDBACK_MESSAGE_MAX).label("Message"),
2794
- email: (0, import_yup24.string)().email().optional().label("Email"),
2795
- pageUrl: (0, import_yup24.string)().optional().label("Page URL"),
2796
- status: (0, import_yup24.mixed)().oneOf(SupportedFeedbackStatuses).optional().label("Status")
3812
+ var import_yup38 = require("yup");
3813
+ var FeedbackSchema = (0, import_yup38.object)({
3814
+ type: (0, import_yup38.mixed)().oneOf(SupportedFeedbackTypes).required().label("Type"),
3815
+ message: (0, import_yup38.string)().trim().required("Please enter a message").max(FEEDBACK_MESSAGE_MAX).label("Message"),
3816
+ email: (0, import_yup38.string)().email().optional().label("Email"),
3817
+ pageUrl: (0, import_yup38.string)().optional().label("Page URL"),
3818
+ status: (0, import_yup38.mixed)().oneOf(SupportedFeedbackStatuses).optional().label("Status")
2797
3819
  }).label("Feedback Schema");
2798
3820
 
3821
+ // src/skill/skill-filter.schema.ts
3822
+ var import_yup39 = require("yup");
3823
+ var SkillFilterShape = (0, import_yup39.object)({
3824
+ /** Display names, matched exactly. Free-text matching is `?search=`. */
3825
+ names: tokens("Names"),
3826
+ /**
3827
+ * Taxonomy concept ids, e.g. "power-bi".
3828
+ *
3829
+ * `tokens`, NOT `codeTokens`: a conceptId is a lowercase slug, and
3830
+ * uppercasing it would match nothing.
3831
+ */
3832
+ conceptIds: tokens("Concept IDs"),
3833
+ tags: tokens("Tags")
3834
+ }).label("Skill Filter");
3835
+ var SkillFilterSchema = rejectUnknownKeys(
3836
+ SkillFilterShape,
3837
+ () => Object.keys(SkillFilterShape.fields)
3838
+ );
3839
+
3840
+ // src/billing/billing.schema.ts
3841
+ var import_yup40 = require("yup");
3842
+
3843
+ // src/billing/billing.constant.ts
3844
+ var PlanKind = /* @__PURE__ */ ((PlanKind2) => {
3845
+ PlanKind2["Subscription"] = "subscription";
3846
+ PlanKind2["OneOff"] = "one-off";
3847
+ return PlanKind2;
3848
+ })(PlanKind || {});
3849
+ var SupportedPlanKinds = Object.values(PlanKind);
3850
+ var PlanStatus = /* @__PURE__ */ ((PlanStatus2) => {
3851
+ PlanStatus2["Draft"] = "draft";
3852
+ PlanStatus2["Active"] = "active";
3853
+ PlanStatus2["Archived"] = "archived";
3854
+ return PlanStatus2;
3855
+ })(PlanStatus || {});
3856
+ var SupportedPlanStatuses = Object.values(PlanStatus);
3857
+ var GrantType = /* @__PURE__ */ ((GrantType2) => {
3858
+ GrantType2["Boolean"] = "boolean";
3859
+ GrantType2["Quota"] = "quota";
3860
+ GrantType2["Credit"] = "credit";
3861
+ return GrantType2;
3862
+ })(GrantType || {});
3863
+ var SupportedGrantTypes = Object.values(GrantType);
3864
+ var QuotaPeriod = /* @__PURE__ */ ((QuotaPeriod2) => {
3865
+ QuotaPeriod2["Month"] = "month";
3866
+ QuotaPeriod2["Year"] = "year";
3867
+ QuotaPeriod2["Lifetime"] = "lifetime";
3868
+ return QuotaPeriod2;
3869
+ })(QuotaPeriod || {});
3870
+ var SupportedQuotaPeriods = Object.values(QuotaPeriod);
3871
+ var BillingPeriod = /* @__PURE__ */ ((BillingPeriod2) => {
3872
+ BillingPeriod2["Month"] = "month";
3873
+ BillingPeriod2["Year"] = "year";
3874
+ return BillingPeriod2;
3875
+ })(BillingPeriod || {});
3876
+ var SupportedBillingPeriods = Object.values(BillingPeriod);
3877
+ var SubscriptionStatus = /* @__PURE__ */ ((SubscriptionStatus2) => {
3878
+ SubscriptionStatus2["Active"] = "active";
3879
+ SubscriptionStatus2["Trialing"] = "trialing";
3880
+ SubscriptionStatus2["PastDue"] = "past_due";
3881
+ SubscriptionStatus2["Paused"] = "paused";
3882
+ SubscriptionStatus2["Canceled"] = "canceled";
3883
+ return SubscriptionStatus2;
3884
+ })(SubscriptionStatus || {});
3885
+ var SupportedSubscriptionStatuses = Object.values(SubscriptionStatus);
3886
+ var EntitlingSubscriptionStatuses = [
3887
+ "active" /* Active */,
3888
+ "trialing" /* Trialing */,
3889
+ "past_due" /* PastDue */
3890
+ ];
3891
+ var TransactionStatus = /* @__PURE__ */ ((TransactionStatus2) => {
3892
+ TransactionStatus2["Draft"] = "draft";
3893
+ TransactionStatus2["Ready"] = "ready";
3894
+ TransactionStatus2["Billed"] = "billed";
3895
+ TransactionStatus2["Paid"] = "paid";
3896
+ TransactionStatus2["Completed"] = "completed";
3897
+ TransactionStatus2["Canceled"] = "canceled";
3898
+ TransactionStatus2["PastDue"] = "past_due";
3899
+ return TransactionStatus2;
3900
+ })(TransactionStatus || {});
3901
+ var SupportedTransactionStatuses = Object.values(TransactionStatus);
3902
+ var PaddleEnvironment = /* @__PURE__ */ ((PaddleEnvironment2) => {
3903
+ PaddleEnvironment2["Sandbox"] = "sandbox";
3904
+ PaddleEnvironment2["Production"] = "production";
3905
+ return PaddleEnvironment2;
3906
+ })(PaddleEnvironment || {});
3907
+ var SupportedPaddleEnvironments = Object.values(PaddleEnvironment);
3908
+ var FeatureKey = /* @__PURE__ */ ((FeatureKey2) => {
3909
+ FeatureKey2["JobPost"] = "job.post";
3910
+ FeatureKey2["JobAlerts"] = "job.alerts";
3911
+ FeatureKey2["PageCreate"] = "page.create";
3912
+ FeatureKey2["GroupCreate"] = "group.create";
3913
+ FeatureKey2["CandidateFinder"] = "candidate.finder";
3914
+ FeatureKey2["SearchAdvanced"] = "search.advanced";
3915
+ FeatureKey2["GetDiscovered"] = "get-discovered";
3916
+ return FeatureKey2;
3917
+ })(FeatureKey || {});
3918
+ var SupportedFeatureKeys = Object.values(FeatureKey);
3919
+ var EntitlementDenialReason = /* @__PURE__ */ ((EntitlementDenialReason2) => {
3920
+ EntitlementDenialReason2["NotEntitled"] = "not-entitled";
3921
+ EntitlementDenialReason2["QuotaExhausted"] = "quota-exhausted";
3922
+ EntitlementDenialReason2["NoCredits"] = "no-credits";
3923
+ return EntitlementDenialReason2;
3924
+ })(EntitlementDenialReason || {});
3925
+ var SupportedEntitlementDenialReasons = Object.values(
3926
+ EntitlementDenialReason
3927
+ );
3928
+ var GRANT_MAX_LIMIT = 1e5;
3929
+ var CONSUME_MAX_UNITS = 1e3;
3930
+
3931
+ // src/billing/billing.schema.ts
3932
+ var MoneySchema = (0, import_yup40.object)({
3933
+ amountMinor: (0, import_yup40.number)().integer().min(0).required().label("Amount (minor units)"),
3934
+ /**
3935
+ * ISO-4217, as returned by Paddle. Deliberately NOT constrained to
3936
+ * `SupportedSalaryCurrencies`: that list is the job-salary vocabulary, and
3937
+ * Paddle bills in currencies it does not contain. Constraining it here would
3938
+ * reject real payments.
3939
+ */
3940
+ currencyCode: (0, import_yup40.string)().trim().uppercase().required().label("Currency")
3941
+ }).label("Money");
3942
+ var PlanGrantSchema = (0, import_yup40.object)({
3943
+ /**
3944
+ * The gated capability, e.g. `job.post`. A free string on purpose: see the
3945
+ * note on `FeatureKey` in `billing.constant.ts`. Adding a feature to the
3946
+ * paywall must not require releasing this package.
3947
+ */
3948
+ feature: (0, import_yup40.string)().trim().required().min(1).max(64).label("Feature"),
3949
+ type: (0, import_yup40.string)().oneOf(SupportedGrantTypes).required().default("boolean" /* Boolean */).label("Grant Type"),
3950
+ /** Uses per period. Required for `quota`, meaningless otherwise. */
3951
+ limit: (0, import_yup40.number)().integer().min(1).max(GRANT_MAX_LIMIT).optional().label("Limit").when("type", {
3952
+ is: "quota" /* Quota */,
3953
+ then: (s) => s.required("A quota grant needs a limit")
3954
+ }),
3955
+ period: (0, import_yup40.string)().oneOf(SupportedQuotaPeriods).optional().default("month" /* Month */).label("Period").when("type", {
3956
+ is: "quota" /* Quota */,
3957
+ then: (s) => s.required("A quota grant needs a period")
3958
+ }),
3959
+ /** Non-expiring uses granted per purchase. Required for `credit`. */
3960
+ amount: (0, import_yup40.number)().integer().min(1).max(GRANT_MAX_LIMIT).optional().label("Amount").when("type", {
3961
+ is: "credit" /* Credit */,
3962
+ then: (s) => s.required("A credit grant needs an amount")
3963
+ })
3964
+ }).label("Plan Grant");
3965
+ var PaddlePriceIdsSchema = (0, import_yup40.object)({
3966
+ sandbox: (0, import_yup40.array)().of((0, import_yup40.string)().required()).optional().default([]).label("Sandbox Price IDs"),
3967
+ live: (0, import_yup40.array)().of((0, import_yup40.string)().required()).optional().default([]).label("Live Price IDs")
3968
+ }).label("Paddle Price IDs");
3969
+ var PlanSchema = (0, import_yup40.object)({
3970
+ /** Stable slug, e.g. `employer-pro`. The identifier everything else references. */
3971
+ key: (0, import_yup40.string)().trim().lowercase().required().min(2).max(64).label("Key"),
3972
+ name: (0, import_yup40.string)().required().min(1).max(120).label("Name"),
3973
+ description: (0, import_yup40.string)().optional().max(500).label("Description"),
3974
+ kind: (0, import_yup40.string)().oneOf(SupportedPlanKinds).required().default("subscription" /* Subscription */).label("Kind"),
3975
+ status: (0, import_yup40.string)().oneOf(SupportedPlanStatuses).required().default("draft" /* Draft */).label("Status"),
3976
+ paddlePriceIds: PaddlePriceIdsSchema.default(
3977
+ () => PaddlePriceIdsSchema.getDefault()
3978
+ ),
3979
+ grants: (0, import_yup40.array)().of(PlanGrantSchema.required()).required().min(1, "A plan must grant at least one feature").label("Grants"),
3980
+ /** Display price for the pricing page. Billing is always Paddle's own figure. */
3981
+ price: MoneySchema.optional().default(void 0).label("Price"),
3982
+ /**
3983
+ * How often this plan bills, for the pricing page's monthly/yearly toggle and
3984
+ * for rendering `price` with the right suffix.
3985
+ *
3986
+ * Display only: Paddle owns the real billing interval, and this never drives a
3987
+ * charge. It is optional because one-off plans have no interval, and because
3988
+ * plans seeded before this field existed must keep validating. Treat absent on
3989
+ * a subscription as "monthly" at the use site rather than defaulting it here,
3990
+ * so an unset value stays visible as unset.
3991
+ */
3992
+ billingPeriod: (0, import_yup40.string)().oneOf(SupportedBillingPeriods).optional().label("Billing Period"),
3993
+ /** Ordering on the pricing page, ascending. */
3994
+ sortOrder: (0, import_yup40.number)().integer().optional().default(0).label("Sort Order"),
3995
+ createdAt: (0, import_yup40.number)().optional().label("Created At"),
3996
+ updatedAt: (0, import_yup40.number)().optional().label("Updated At")
3997
+ }).label("Plan");
3998
+ var BillingSubscriptionSchema = (0, import_yup40.object)({
3999
+ userId: (0, import_yup40.string)().required().label("User ID"),
4000
+ paddleSubscriptionId: (0, import_yup40.string)().required().label("Paddle Subscription ID"),
4001
+ paddleCustomerId: (0, import_yup40.string)().required().label("Paddle Customer ID"),
4002
+ planKey: (0, import_yup40.string)().required().label("Plan Key"),
4003
+ priceIds: (0, import_yup40.array)().of((0, import_yup40.string)().required()).required().label("Price IDs"),
4004
+ status: (0, import_yup40.string)().oneOf(SupportedSubscriptionStatuses).required().label("Status"),
4005
+ /** Start of the period the quota is anchored to. */
4006
+ currentPeriodStart: (0, import_yup40.number)().optional().label("Current Period Start"),
4007
+ currentPeriodEnd: (0, import_yup40.number)().optional().label("Current Period End"),
4008
+ /** Set when a cancellation is scheduled; access continues until the period ends. */
4009
+ canceledAt: (0, import_yup40.number)().optional().label("Canceled At"),
4010
+ /** `occurred_at` of the newest Paddle event applied. Guards out-of-order writes. */
4011
+ lastEventAt: (0, import_yup40.number)().required().default(0).label("Last Event At"),
4012
+ createdAt: (0, import_yup40.number)().optional().label("Created At"),
4013
+ updatedAt: (0, import_yup40.number)().optional().label("Updated At")
4014
+ }).label("Billing Subscription");
4015
+ var BillingTransactionSchema = (0, import_yup40.object)({
4016
+ userId: (0, import_yup40.string)().required().label("User ID"),
4017
+ paddleTransactionId: (0, import_yup40.string)().required().label("Paddle Transaction ID"),
4018
+ paddleCustomerId: (0, import_yup40.string)().optional().label("Paddle Customer ID"),
4019
+ paddleSubscriptionId: (0, import_yup40.string)().optional().label("Paddle Subscription ID"),
4020
+ planKey: (0, import_yup40.string)().optional().label("Plan Key"),
4021
+ priceIds: (0, import_yup40.array)().of((0, import_yup40.string)().required()).required().label("Price IDs"),
4022
+ status: (0, import_yup40.string)().oneOf(SupportedTransactionStatuses).required().label("Status"),
4023
+ total: MoneySchema.optional().default(void 0).label("Total"),
4024
+ billedAt: (0, import_yup40.number)().optional().label("Billed At"),
4025
+ lastEventAt: (0, import_yup40.number)().required().default(0).label("Last Event At"),
4026
+ createdAt: (0, import_yup40.number)().optional().label("Created At"),
4027
+ updatedAt: (0, import_yup40.number)().optional().label("Updated At")
4028
+ }).label("Billing Transaction");
4029
+ var FeatureEntitlementSchema = (0, import_yup40.object)({
4030
+ feature: (0, import_yup40.string)().required().label("Feature"),
4031
+ type: (0, import_yup40.string)().oneOf(SupportedGrantTypes).required().label("Grant Type"),
4032
+ /** Whether the feature may be used AT ALL right now. */
4033
+ allowed: (0, import_yup40.boolean)().required().default(false).label("Allowed"),
4034
+ /** Quota ceiling for the period. Absent for boolean grants. */
4035
+ limit: (0, import_yup40.number)().integer().optional().label("Limit"),
4036
+ used: (0, import_yup40.number)().integer().optional().label("Used"),
4037
+ /** Quota remaining plus any credit balance. Absent for boolean grants. */
4038
+ remaining: (0, import_yup40.number)().integer().optional().label("Remaining"),
4039
+ /** Non-expiring balance from one-off purchases. */
4040
+ credits: (0, import_yup40.number)().integer().optional().label("Credits"),
4041
+ /** When the quota next refreshes. */
4042
+ resetsAt: (0, import_yup40.number)().optional().label("Resets At")
4043
+ }).label("Feature Entitlement");
4044
+ var EntitlementDenialSchema = (0, import_yup40.object)({
4045
+ feature: (0, import_yup40.string)().required().label("Feature"),
4046
+ reason: (0, import_yup40.string)().oneOf(SupportedEntitlementDenialReasons).required().label("Reason"),
4047
+ limit: (0, import_yup40.number)().integer().optional().label("Limit"),
4048
+ used: (0, import_yup40.number)().integer().optional().label("Used"),
4049
+ resetsAt: (0, import_yup40.number)().optional().label("Resets At"),
4050
+ upgradeUrl: (0, import_yup40.string)().optional().label("Upgrade URL")
4051
+ }).label("Entitlement Denial");
4052
+ var EntitlementConsumeSchema = (0, import_yup40.object)({
4053
+ userId: (0, import_yup40.string)().required().label("User ID"),
4054
+ feature: (0, import_yup40.string)().trim().required().min(1).max(64).label("Feature"),
4055
+ units: (0, import_yup40.number)().integer().min(1).max(CONSUME_MAX_UNITS).required().default(1).label("Units"),
4056
+ /**
4057
+ * Caller-supplied de-duplication key. Makes a spend exactly-once across the
4058
+ * retries that at-least-once delivery and ordinary network failures produce.
4059
+ */
4060
+ idempotencyKey: (0, import_yup40.string)().trim().required().min(8).max(128).label("Idempotency Key")
4061
+ }).label("Entitlement Consume");
4062
+
2799
4063
  // src/user/user.schema.ts
2800
- var import_yup37 = require("yup");
4064
+ var import_yup53 = require("yup");
2801
4065
 
2802
4066
  // src/user/work-experience.schema.ts
2803
- var import_yup25 = require("yup");
2804
- var WorkExperienceSchema = (0, import_yup25.object)().shape({
4067
+ var import_yup41 = require("yup");
4068
+ var WorkExperienceSchema = (0, import_yup41.object)().shape({
2805
4069
  company: PageSchema.pick(["name", "slug", "type", "logo"]).deepPartial().concat(PageSchema.pick(["name", "type"])).required().label("Company"),
2806
- designation: (0, import_yup25.string)().trim().required().label("Designation"),
4070
+ designation: (0, import_yup41.string)().trim().required().label("Designation"),
2807
4071
  duration: DurationSchema({
2808
4072
  format: "YYYY-MM",
2809
4073
  startLabel: "Start Date",
2810
4074
  endLabel: "End Date"
2811
4075
  }).required().label("Duration"),
2812
- description: (0, import_yup25.string)().trim().max(3e3).optional().label("Description"),
4076
+ description: (0, import_yup41.string)().trim().max(3e3).optional().label("Description"),
2813
4077
  location: LocationSchema.required().label("Location"),
2814
- isRemote: (0, import_yup25.boolean)().oneOf([true, false]).default(false).label("Is Remote")
4078
+ isRemote: (0, import_yup41.boolean)().oneOf([true, false]).default(false).label("Is Remote")
2815
4079
  }).noUnknown().strict().label("Work Experience");
2816
4080
 
2817
4081
  // src/user/education.schema.ts
2818
- var import_yup26 = require("yup");
2819
- var EducationSchema = (0, import_yup26.object)({
4082
+ var import_yup42 = require("yup");
4083
+ var EducationSchema = (0, import_yup42.object)({
2820
4084
  institute: PageSchema.pick(["name", "slug", "type", "logo"]).deepPartial().concat(PageSchema.pick(["name", "type"])).required().label("Institute"),
2821
- course: (0, import_yup26.string)().trim().required().label("Course"),
2822
- fieldOfStudy: (0, import_yup26.string)().trim().required().label("Field of Study"),
4085
+ course: (0, import_yup42.string)().trim().required().label("Course"),
4086
+ fieldOfStudy: (0, import_yup42.string)().trim().required().label("Field of Study"),
2823
4087
  duration: DurationSchema({
2824
4088
  format: "YYYY-MM",
2825
4089
  startLabel: "Start Date",
2826
4090
  endLabel: "End Date"
2827
4091
  }).required().label("Duration"),
2828
- description: (0, import_yup26.string)().trim().max(3e3).optional().label("Description"),
2829
- isDistanceLearning: (0, import_yup26.boolean)().default(false).label("Is Distance Learning"),
4092
+ description: (0, import_yup42.string)().trim().max(3e3).optional().label("Description"),
4093
+ isDistanceLearning: (0, import_yup42.boolean)().default(false).label("Is Distance Learning"),
2830
4094
  location: LocationSchema.required().label("Location"),
2831
- studyType: (0, import_yup26.string)().oneOf(SupportedStudyTypes).trim().max(50).required().label("Study Type")
4095
+ studyType: (0, import_yup42.string)().oneOf(SupportedStudyTypes).trim().max(50).required().label("Study Type")
2832
4096
  }).noUnknown().strict().label("Education");
2833
4097
 
2834
4098
  // src/user/user-skill.schema.ts
2835
- var import_yup27 = require("yup");
2836
- var UserSkillSchema = (0, import_yup27.object)({
2837
- name: (0, import_yup27.string)().required().label("Name"),
2838
- proficiencyLevel: (0, import_yup27.string)().oneOf(SupportedProficiencyLevels).required().label("Proficiency Level"),
4099
+ var import_yup43 = require("yup");
4100
+ var UserSkillSchema = (0, import_yup43.object)({
4101
+ name: (0, import_yup43.string)().required().label("Name"),
4102
+ proficiencyLevel: (0, import_yup43.string)().oneOf(SupportedProficiencyLevels).required().label("Proficiency Level"),
2839
4103
  lastUsed: dateString().format("YYYY-MM").nullable().optional().label("Last Used")
2840
4104
  }).noUnknown().strict().label("Skill");
2841
4105
 
2842
4106
  // src/user/project.schema.ts
2843
- var import_yup28 = require("yup");
2844
- var UserProjectSchema = (0, import_yup28.object)({
2845
- name: (0, import_yup28.string)().trim().max(50).required().label("Name"),
2846
- url: (0, import_yup28.string)().optional().label("URL"),
2847
- description: (0, import_yup28.string)().trim().min(100).max(3e3).required().label("Description"),
4107
+ var import_yup44 = require("yup");
4108
+ var UserProjectSchema = (0, import_yup44.object)({
4109
+ name: (0, import_yup44.string)().trim().max(50).required().label("Name"),
4110
+ url: (0, import_yup44.string)().optional().label("URL"),
4111
+ description: (0, import_yup44.string)().trim().min(100).max(3e3).required().label("Description"),
2848
4112
  duration: DurationSchema({
2849
4113
  format: "YYYY-MM",
2850
4114
  startLabel: "Start Date",
@@ -2853,61 +4117,68 @@ var UserProjectSchema = (0, import_yup28.object)({
2853
4117
  }).noUnknown().strict().label("Project");
2854
4118
 
2855
4119
  // src/user/user-certification.schema.ts
2856
- var import_yup29 = require("yup");
2857
- var UserCertificationSchema = (0, import_yup29.object)({
2858
- name: (0, import_yup29.string)().trim().max(100).required().label("Name"),
4120
+ var import_yup45 = require("yup");
4121
+ var UserCertificationSchema = (0, import_yup45.object)({
4122
+ name: (0, import_yup45.string)().trim().max(100).required().label("Name"),
2859
4123
  // TODO: Add validation for authority
2860
- authority: (0, import_yup29.string)().trim().max(100).required().label("Authority"),
2861
- licenseNumber: (0, import_yup29.string)().trim().max(50).optional().label("License Number"),
4124
+ authority: (0, import_yup45.string)().trim().max(100).required().label("Authority"),
4125
+ licenseNumber: (0, import_yup45.string)().trim().max(50).optional().label("License Number"),
2862
4126
  duration: DurationSchema({
2863
4127
  format: "YYYY-MM",
2864
4128
  startLabel: "Start Date",
2865
4129
  endLabel: "End Date"
2866
4130
  }).optional().nullable().label("Duration"),
2867
- url: (0, import_yup29.string)().optional().label("URL")
4131
+ url: (0, import_yup45.string)().optional().label("URL")
2868
4132
  }).noUnknown().strict().label("Certification");
2869
4133
 
2870
4134
  // src/user/user-interest.schema.ts
2871
- var import_yup30 = require("yup");
2872
- var UserInterestSchema = (0, import_yup30.string)().trim().required().label("Interest");
4135
+ var import_yup46 = require("yup");
4136
+ var UserInterestSchema = (0, import_yup46.object)({
4137
+ /** The taxonomy entry's id. */
4138
+ id: (0, import_yup46.string)().trim().required().label("Interest Id"),
4139
+ /** Display name, e.g. "Cloud Computing". */
4140
+ name: (0, import_yup46.string)().trim().required().label("Interest"),
4141
+ /** Material-symbols icon name, e.g. "cloud". */
4142
+ logo: (0, import_yup46.string)().trim().required().label("Interest Logo")
4143
+ }).required().label("Interest");
2873
4144
 
2874
4145
  // src/user/user-language.schema.ts
2875
- var import_yup31 = require("yup");
2876
- var UserLanguageSchema = (0, import_yup31.object)({
2877
- name: (0, import_yup31.string)().trim().required().label("Name"),
2878
- proficiencyLevel: (0, import_yup31.string)().oneOf(SupportedProficiencyLevels).trim().max(50).required().label("Proficiency Level")
4146
+ var import_yup47 = require("yup");
4147
+ var UserLanguageSchema = (0, import_yup47.object)({
4148
+ name: (0, import_yup47.string)().trim().required().label("Name"),
4149
+ proficiencyLevel: (0, import_yup47.string)().oneOf(SupportedProficiencyLevels).trim().max(50).required().label("Proficiency Level")
2879
4150
  }).noUnknown().strict().label("Language");
2880
4151
 
2881
4152
  // src/user/user-additional-info.schema.ts
2882
- var import_yup32 = require("yup");
2883
- var UserAdditionalInfoSchema = (0, import_yup32.object)({
2884
- title: (0, import_yup32.string)().trim().max(60).required().label("Title"),
2885
- description: (0, import_yup32.string)().trim().max(1e3).required().label("Description")
4153
+ var import_yup48 = require("yup");
4154
+ var UserAdditionalInfoSchema = (0, import_yup48.object)({
4155
+ title: (0, import_yup48.string)().trim().max(60).required().label("Title"),
4156
+ description: (0, import_yup48.string)().trim().max(1e3).required().label("Description")
2886
4157
  }).noUnknown().strict().label("User Additional Info");
2887
4158
 
2888
4159
  // src/user/general-detail.schema.ts
2889
- var import_yup33 = require("yup");
2890
- var UserGeneralDetailSchema = (0, import_yup33.object)({
2891
- id: (0, import_yup33.string)().optional().label("ID"),
2892
- name: (0, import_yup33.object)().shape({
2893
- first: (0, import_yup33.string)().trim().max(50).required().label("First Name"),
2894
- last: (0, import_yup33.string)().trim().max(50).optional().label("Last Name")
4160
+ var import_yup49 = require("yup");
4161
+ var UserGeneralDetailSchema = (0, import_yup49.object)({
4162
+ id: (0, import_yup49.string)().optional().label("ID"),
4163
+ name: (0, import_yup49.object)().shape({
4164
+ first: (0, import_yup49.string)().trim().max(50).required().label("First Name"),
4165
+ last: (0, import_yup49.string)().trim().max(50).optional().label("Last Name")
2895
4166
  }).required().label("Name"),
2896
- headline: (0, import_yup33.string)().trim().max(200).optional().label("Headline"),
2897
- image: (0, import_yup33.string)().optional().label("Image"),
4167
+ headline: (0, import_yup49.string)().trim().max(200).optional().label("Headline"),
4168
+ image: (0, import_yup49.string)().optional().label("Image"),
2898
4169
  // The DISPLAYED avatar (`image`) is a flattened export with any status frame
2899
4170
  // ("#OpenToWork", …) already burned into the pixels. `imageOriginal` keeps the
2900
4171
  // un-framed source the user uploaded, so the image builder can reopen it and
2901
4172
  // apply a DIFFERENT frame instead of stacking a second one on top of the first.
2902
4173
  // Optional and absent on every pre-existing user: treat a missing value as
2903
4174
  // "only the flattened image is available".
2904
- imageOriginal: (0, import_yup33.string)().optional().label("Original Image"),
2905
- aboutMe: (0, import_yup33.string)().trim().max(3e3).optional().label("About Me"),
2906
- email: (0, import_yup33.string)().required().label("Email"),
2907
- mobile: (0, import_yup33.string)().nullable().optional().label("Mobile"),
2908
- emailVerified: (0, import_yup33.string)().nullable().optional().label("Email Verified"),
2909
- mobileVerified: (0, import_yup33.string)().nullable().optional().label("Mobile Verified"),
2910
- experienceLevel: (0, import_yup33.string)().oneOf(SupportedExperienceLevels).required().label("Experience level"),
4175
+ imageOriginal: (0, import_yup49.string)().optional().label("Original Image"),
4176
+ aboutMe: (0, import_yup49.string)().trim().max(3e3).optional().label("About Me"),
4177
+ email: (0, import_yup49.string)().required().label("Email"),
4178
+ mobile: (0, import_yup49.string)().nullable().optional().label("Mobile"),
4179
+ emailVerified: (0, import_yup49.string)().nullable().optional().label("Email Verified"),
4180
+ mobileVerified: (0, import_yup49.string)().nullable().optional().label("Mobile Verified"),
4181
+ experienceLevel: (0, import_yup49.string)().oneOf(SupportedExperienceLevels).required().label("Experience level"),
2911
4182
  /**
2912
4183
  * The user's HIGHEST qualification, e.g. `bachelor`.
2913
4184
  *
@@ -2926,7 +4197,7 @@ var UserGeneralDetailSchema = (0, import_yup33.object)({
2926
4197
  * UNCONSTRAINED on education rather than filtering the user out — the same
2927
4198
  * opt-out reasoning `notificationPrefs` uses.
2928
4199
  */
2929
- educationLevel: (0, import_yup33.string)().oneOf(SupportedEducationLevels).optional().label("Education level"),
4200
+ educationLevel: (0, import_yup49.string)().oneOf(SupportedEducationLevels).optional().label("Education level"),
2930
4201
  location: LocationSchema.required().label("Location"),
2931
4202
  /**
2932
4203
  * Where the account was created, captured once and never rewritten.
@@ -2942,38 +4213,38 @@ var UserGeneralDetailSchema = (0, import_yup33.object)({
2942
4213
  * - `location` derived from the user's self-entered profile location
2943
4214
  * - `default` assumed (no signal available); presumption, not evidence
2944
4215
  */
2945
- signupContext: (0, import_yup33.object)().shape({
4216
+ signupContext: (0, import_yup49.object)().shape({
2946
4217
  // ISO 3166-1 alpha-2, uppercase — the shape `cf-ipcountry` returns, so
2947
4218
  // measured and inferred values are directly comparable.
2948
- country: (0, import_yup33.string)().trim().uppercase().length(2).required().label("Signup Country"),
2949
- locale: (0, import_yup33.string)().trim().required().label("Signup Locale"),
2950
- source: (0, import_yup33.string)().oneOf(SupportedSignupContextSources).required().label("Signup Context Source"),
2951
- at: (0, import_yup33.number)().required().label("Signup Context At")
4219
+ country: (0, import_yup49.string)().trim().uppercase().length(2).required().label("Signup Country"),
4220
+ locale: (0, import_yup49.string)().trim().required().label("Signup Locale"),
4221
+ source: (0, import_yup49.string)().oneOf(SupportedSignupContextSources).required().label("Signup Context Source"),
4222
+ at: (0, import_yup49.number)().required().label("Signup Context At")
2952
4223
  }).optional().default(void 0).label("Signup Context"),
2953
- profileVisibility: (0, import_yup33.string)().oneOf(SupportedUserProfileVisibilities).default("public" /* Public */).label("Profile Visibility")
4224
+ profileVisibility: (0, import_yup49.string)().oneOf(SupportedUserProfileVisibilities).default("public" /* Public */).label("Profile Visibility")
2954
4225
  }).noUnknown().strict().label("General Detail");
2955
4226
 
2956
4227
  // src/user/user-job-preferences.schema.ts
2957
- var import_yup34 = require("yup");
2958
- var UserJobPreferencesSchema = (0, import_yup34.object)({
4228
+ var import_yup50 = require("yup");
4229
+ var UserJobPreferencesSchema = (0, import_yup50.object)({
2959
4230
  /**
2960
4231
  * Whether the job seeker is openly signalling availability. Surfaces the
2961
4232
  * "Open to work" badge on the public profile and boosts visibility in
2962
4233
  * recruiter search. Defaults to `false` - users must opt in.
2963
4234
  */
2964
- openToWork: (0, import_yup34.boolean)().default(false).label("Open to Work"),
4235
+ openToWork: (0, import_yup50.boolean)().default(false).label("Open to Work"),
2965
4236
  /**
2966
4237
  * How urgently the user is looking for their next job. Drives match scoring
2967
4238
  * and can downgrade the visibility of expired or low-relevance postings for
2968
4239
  * users in `passively_browsing` mode.
2969
4240
  */
2970
- jobSearchUrgency: (0, import_yup34.string)().oneOf(SupportedJobSearchUrgencies).required().label("Job Search Urgency"),
4241
+ jobSearchUrgency: (0, import_yup50.string)().oneOf(SupportedJobSearchUrgencies).required().label("Job Search Urgency"),
2971
4242
  /**
2972
4243
  * Locations the user wants to work in. Stores the full `LocationSchema`
2973
4244
  * shape (country, city, state, geo, etc.) as returned by the locations
2974
4245
  * autocomplete API.
2975
4246
  */
2976
- jobLocations: (0, import_yup34.array)().of(LocationSchema.required().label("Job Location")).min(1, "Pick at least one preferred location.").required().label("Job Locations"),
4247
+ jobLocations: (0, import_yup50.array)().of(LocationSchema.required().label("Job Location")).min(1, "Pick at least one preferred location.").required().label("Job Locations"),
2977
4248
  /**
2978
4249
  * Seniority levels the user wants jobs to be matched against. Multi-select
2979
4250
  * (up to 2) for borderline candidates.
@@ -2984,7 +4255,7 @@ var UserJobPreferencesSchema = (0, import_yup34.object)({
2984
4255
  * Both reuse the same `ExperienceLevel` taxonomy from `common.constant.ts`
2985
4256
  * so search/match logic doesn't have to translate between two vocabularies.
2986
4257
  */
2987
- targetExperienceLevels: (0, import_yup34.array)().of((0, import_yup34.string)().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"),
4258
+ targetExperienceLevels: (0, import_yup50.array)().of((0, import_yup50.string)().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"),
2988
4259
  /**
2989
4260
  * Specializations the user is searching for - free-form titles.
2990
4261
  *
@@ -2993,19 +4264,19 @@ var UserJobPreferencesSchema = (0, import_yup34.object)({
2993
4264
  * surfaces a curated "Popular Roles" picker for discovery, but the user
2994
4265
  * can ultimately add any title; matching/normalization happens downstream.
2995
4266
  */
2996
- jobRoles: (0, import_yup34.array)().of((0, import_yup34.string)().trim().max(120).required()).min(1, "Pick at least one role.").required().label("Job Roles"),
4267
+ jobRoles: (0, import_yup50.array)().of((0, import_yup50.string)().trim().max(120).required()).min(1, "Pick at least one role.").required().label("Job Roles"),
2997
4268
  /**
2998
4269
  * Minimum acceptable annual base salary, in `minSalaryCurrency`.
2999
4270
  * Stored as an integer in the currency's major unit (e.g. dollars, not cents).
3000
4271
  * Defaults to 0 ("any salary") rather than being optional - every job-seeker
3001
4272
  * has a floor, even if it's zero.
3002
4273
  */
3003
- minSalary: (0, import_yup34.number)().integer().min(MIN_SALARY_LOWER_BOUND).max(MIN_SALARY_UPPER_BOUND).default(0).required().label("Minimum Salary"),
3004
- minSalaryCurrency: (0, import_yup34.string)().oneOf(SupportedSalaryCurrencies).default("USD").required().label("Minimum Salary Currency"),
4274
+ minSalary: (0, import_yup50.number)().integer().min(MIN_SALARY_LOWER_BOUND).max(MIN_SALARY_UPPER_BOUND).default(0).required().label("Minimum Salary"),
4275
+ minSalaryCurrency: (0, import_yup50.string)().oneOf(SupportedSalaryCurrencies).default("USD").required().label("Minimum Salary Currency"),
3005
4276
  /**
3006
4277
  * Marketing-attribution capture from the final onboarding step.
3007
4278
  */
3008
- referralSource: (0, import_yup34.string)().oneOf(SupportedReferralSources).required().label("Referral Source"),
4279
+ referralSource: (0, import_yup50.string)().oneOf(SupportedReferralSources).required().label("Referral Source"),
3009
4280
  /**
3010
4281
  * Resumes the user has uploaded. Embedded directly on the user document
3011
4282
  * (capped at 5) - they're cheap to denormalize, every wizard step that
@@ -3019,25 +4290,25 @@ var UserJobPreferencesSchema = (0, import_yup34.object)({
3019
4290
  * AI resume-builder flow - onboarding uploads land here, AI-built resumes
3020
4291
  * live in their own collection.
3021
4292
  */
3022
- resumes: (0, import_yup34.array)().of(
3023
- (0, import_yup34.object)({
4293
+ resumes: (0, import_yup50.array)().of(
4294
+ (0, import_yup50.object)({
3024
4295
  // Stable id assigned on upload - used to address a specific entry
3025
4296
  // for delete / set-primary operations without exposing the GCS URL
3026
4297
  // as a route key.
3027
- id: (0, import_yup34.string)().required().label("ID"),
3028
- url: (0, import_yup34.string)().required().label("Resume URL"),
3029
- filename: (0, import_yup34.string)().trim().max(255).optional().label("Filename"),
3030
- sizeBytes: (0, import_yup34.number)().integer().min(0).optional().label("Size (bytes)"),
3031
- mimeType: (0, import_yup34.string)().trim().max(120).optional().label("MIME Type"),
3032
- uploadedAt: (0, import_yup34.date)().required().label("Uploaded At"),
3033
- isPrimary: (0, import_yup34.boolean)().default(false).label("Is Primary")
4298
+ id: (0, import_yup50.string)().required().label("ID"),
4299
+ url: (0, import_yup50.string)().required().label("Resume URL"),
4300
+ filename: (0, import_yup50.string)().trim().max(255).optional().label("Filename"),
4301
+ sizeBytes: (0, import_yup50.number)().integer().min(0).optional().label("Size (bytes)"),
4302
+ mimeType: (0, import_yup50.string)().trim().max(120).optional().label("MIME Type"),
4303
+ uploadedAt: (0, import_yup50.date)().required().label("Uploaded At"),
4304
+ isPrimary: (0, import_yup50.boolean)().default(false).label("Is Primary")
3034
4305
  }).noUnknown().strict().label("Resume")
3035
4306
  ).max(5, "You can keep at most 5 resumes on file.").default([]).label("Resumes"),
3036
4307
  /**
3037
4308
  * Set when the user finishes the wizard's last step. Acts as the gate the
3038
4309
  * onboarding hook checks to redirect users who haven't completed setup.
3039
4310
  */
3040
- onboardingCompletedAt: (0, import_yup34.date)().nullable().optional().label("Onboarding Completed At"),
4311
+ onboardingCompletedAt: (0, import_yup50.date)().nullable().optional().label("Onboarding Completed At"),
3041
4312
  /**
3042
4313
  * Parser-suggested values for wizard fields, written by the resume-upload
3043
4314
  * flow. Every parser-derived field lands here - including profile-shaped
@@ -3057,92 +4328,92 @@ var UserJobPreferencesSchema = (0, import_yup34.object)({
3057
4328
  * APIs. If `user.<field>` is empty there, the correct behavior is empty,
3058
4329
  * because the user has not confirmed it yet.
3059
4330
  */
3060
- pendingPrefill: (0, import_yup34.object)({
3061
- headline: (0, import_yup34.string)().trim().optional().label("Pending Headline"),
3062
- aboutMe: (0, import_yup34.string)().trim().optional().label("Pending About Me"),
3063
- mobile: (0, import_yup34.string)().trim().optional().label("Pending Mobile"),
4331
+ pendingPrefill: (0, import_yup50.object)({
4332
+ headline: (0, import_yup50.string)().trim().optional().label("Pending Headline"),
4333
+ aboutMe: (0, import_yup50.string)().trim().optional().label("Pending About Me"),
4334
+ mobile: (0, import_yup50.string)().trim().optional().label("Pending Mobile"),
3064
4335
  location: LocationSchema.optional().default(void 0).label("Pending Location"),
3065
- experienceLevel: (0, import_yup34.string)().oneOf(SupportedExperienceLevels).optional().label("Pending Experience Level"),
3066
- socialAccounts: (0, import_yup34.array)().of(SocialAccountSchema.required()).optional().label("Pending Social Accounts"),
3067
- workExperiences: (0, import_yup34.array)().of(WorkExperienceSchema.required()).optional().label("Pending Work Experiences"),
3068
- educations: (0, import_yup34.array)().of(EducationSchema.required()).optional().label("Pending Educations")
4336
+ experienceLevel: (0, import_yup50.string)().oneOf(SupportedExperienceLevels).optional().label("Pending Experience Level"),
4337
+ socialAccounts: (0, import_yup50.array)().of(SocialAccountSchema.required()).optional().label("Pending Social Accounts"),
4338
+ workExperiences: (0, import_yup50.array)().of(WorkExperienceSchema.required()).optional().label("Pending Work Experiences"),
4339
+ educations: (0, import_yup50.array)().of(EducationSchema.required()).optional().label("Pending Educations")
3069
4340
  }).nullable().optional().default(void 0).label("Pending Prefill")
3070
4341
  }).noUnknown().strict().label("User Job Preferences Schema");
3071
4342
 
3072
4343
  // src/user/user-recruiter-profile.schema.ts
3073
- var import_yup35 = require("yup");
3074
- var RecruiterPageLinkSchema = (0, import_yup35.object)({
4344
+ var import_yup51 = require("yup");
4345
+ var RecruiterPageLinkSchema = (0, import_yup51.object)({
3075
4346
  page: PageSchema.pick(["name", "slug", "type", "logo"]).deepPartial().concat(PageSchema.pick(["name", "type"])).required().label("Company Page"),
3076
- jobTitle: (0, import_yup35.string)().trim().max(100).optional().label("Job Title")
4347
+ jobTitle: (0, import_yup51.string)().trim().max(100).optional().label("Job Title")
3077
4348
  }).noUnknown().strict().label("Recruiter Page Link");
3078
- var UserRecruiterProfileSchema = (0, import_yup35.object)({
3079
- recruiterProfile: (0, import_yup35.object)({
3080
- hiringFor: (0, import_yup35.array)().of(RecruiterPageLinkSchema).default([]).label("Hiring For")
4349
+ var UserRecruiterProfileSchema = (0, import_yup51.object)({
4350
+ recruiterProfile: (0, import_yup51.object)({
4351
+ hiringFor: (0, import_yup51.array)().of(RecruiterPageLinkSchema).default([]).label("Hiring For")
3081
4352
  }).optional().default(void 0).label("Recruiter Profile")
3082
4353
  }).noUnknown().strict().label("User Recruiter Profile");
3083
4354
 
3084
4355
  // src/user/user-coordinator-profile.schema.ts
3085
- var import_yup36 = require("yup");
3086
- var CoordinatorPageLinkSchema = (0, import_yup36.object)({
4356
+ var import_yup52 = require("yup");
4357
+ var CoordinatorPageLinkSchema = (0, import_yup52.object)({
3087
4358
  page: PageSchema.pick(["name", "slug", "type", "logo"]).deepPartial().concat(PageSchema.pick(["name", "type"])).required().label("Institute Page"),
3088
- jobTitle: (0, import_yup36.string)().trim().max(100).optional().label("Job Title")
4359
+ jobTitle: (0, import_yup52.string)().trim().max(100).optional().label("Job Title")
3089
4360
  }).noUnknown().strict().label("Coordinator Page Link");
3090
- var UserCoordinatorProfileSchema = (0, import_yup36.object)({
3091
- coordinatorProfile: (0, import_yup36.object)({
3092
- coordinatesAt: (0, import_yup36.array)().of(CoordinatorPageLinkSchema).default([]).label("Coordinates At")
4361
+ var UserCoordinatorProfileSchema = (0, import_yup52.object)({
4362
+ coordinatorProfile: (0, import_yup52.object)({
4363
+ coordinatesAt: (0, import_yup52.array)().of(CoordinatorPageLinkSchema).default([]).label("Coordinates At")
3093
4364
  }).optional().default(void 0).label("Coordinator Profile")
3094
4365
  }).noUnknown().strict().label("User Coordinator Profile");
3095
4366
 
3096
4367
  // src/user/user.schema.ts
3097
- var UserSchema = (0, import_yup37.object)({
4368
+ var UserSchema = (0, import_yup53.object)({
3098
4369
  /**
3099
4370
  * Related auth account id from auth Server (e.g. Better auth https://auth.thejob.dev)
3100
4371
  */
3101
- authAccountId: (0, import_yup37.string)().required().label("Auth Account ID"),
4372
+ authAccountId: (0, import_yup53.string)().required().label("Auth Account ID"),
3102
4373
  /**
3103
4374
  * Social media information about the user (e.g. LinkedIn, GitHub, etc.)
3104
4375
  */
3105
- socialAccounts: (0, import_yup37.array)().of(SocialAccountSchema).default([]).label("Social Accounts"),
4376
+ socialAccounts: (0, import_yup53.array)().of(SocialAccountSchema).default([]).label("Social Accounts"),
3106
4377
  /**
3107
4378
  * Work experience information about the user
3108
4379
  */
3109
- workExperiences: (0, import_yup37.array)().of(WorkExperienceSchema).required().default([]).label("Work Experiences"),
4380
+ workExperiences: (0, import_yup53.array)().of(WorkExperienceSchema).required().default([]).label("Work Experiences"),
3110
4381
  /**
3111
4382
  * Education information about the user
3112
4383
  */
3113
- educations: (0, import_yup37.array)().of(EducationSchema).required().default([]).label("Educations"),
4384
+ educations: (0, import_yup53.array)().of(EducationSchema).required().default([]).label("Educations"),
3114
4385
  /**
3115
4386
  * Skills information about the user
3116
4387
  */
3117
- skills: (0, import_yup37.array)().of(UserSkillSchema).required().default([]).label("Skills"),
4388
+ skills: (0, import_yup53.array)().of(UserSkillSchema).required().default([]).label("Skills"),
3118
4389
  /**
3119
4390
  * Projects information about the user
3120
4391
  */
3121
- projects: (0, import_yup37.array)().of(UserProjectSchema).default([]).label("Projects"),
4392
+ projects: (0, import_yup53.array)().of(UserProjectSchema).default([]).label("Projects"),
3122
4393
  /**
3123
4394
  * Certifications information about the user
3124
4395
  */
3125
- certifications: (0, import_yup37.array)().of(UserCertificationSchema).default([]).label("Certifications"),
4396
+ certifications: (0, import_yup53.array)().of(UserCertificationSchema).default([]).label("Certifications"),
3126
4397
  /**
3127
4398
  * Interests information about the user.
3128
4399
  */
3129
- interests: (0, import_yup37.array)().of(UserInterestSchema).optional().default([]).label("Interests"),
4400
+ interests: (0, import_yup53.array)().of(UserInterestSchema).optional().default([]).label("Interests"),
3130
4401
  /**
3131
4402
  * Languages information about the user
3132
4403
  */
3133
- languages: (0, import_yup37.array)().of(UserLanguageSchema).required().default([]).label("Languages"),
4404
+ languages: (0, import_yup53.array)().of(UserLanguageSchema).required().default([]).label("Languages"),
3134
4405
  /**
3135
4406
  * Additional information about the user
3136
4407
  */
3137
- additionalInfo: (0, import_yup37.array)().of(UserAdditionalInfoSchema).default([]).label("Additional Information"),
4408
+ additionalInfo: (0, import_yup53.array)().of(UserAdditionalInfoSchema).default([]).label("Additional Information"),
3138
4409
  /**
3139
4410
  * Status of the user account
3140
4411
  */
3141
- status: (0, import_yup37.string)().oneOf(SupportedUserStatuses).required().label("Status"),
4412
+ status: (0, import_yup53.string)().oneOf(SupportedUserStatuses).required().label("Status"),
3142
4413
  /**
3143
4414
  * Roles assigned to the user
3144
4415
  */
3145
- roles: (0, import_yup37.array)().of((0, import_yup37.string)().oneOf(SupportedUserRoles)).required().default(DefaultUserRoles).label("Roles"),
4416
+ roles: (0, import_yup53.array)().of((0, import_yup53.string)().oneOf(SupportedUserRoles)).required().default(DefaultUserRoles).label("Roles"),
3146
4417
  /**
3147
4418
  * Explicit consent to receive marketing email (newsletters, job alerts by
3148
4419
  * broadcast, product updates). Marketing sends are HARD-GATED on
@@ -3154,18 +4425,18 @@ var UserSchema = (0, import_yup37.object)({
3154
4425
  * unsubscribe sets `subscribed=false` so consent and the email-service
3155
4426
  * suppression list stay consistent. Defaults to NOT subscribed (opt-in).
3156
4427
  */
3157
- marketingConsent: (0, import_yup37.object)({
3158
- subscribed: (0, import_yup37.boolean)().default(false).label("Subscribed to marketing email"),
4428
+ marketingConsent: (0, import_yup53.object)({
4429
+ subscribed: (0, import_yup53.boolean)().default(false).label("Subscribed to marketing email"),
3159
4430
  /** Epoch ms of the last consent change (opt-in or opt-out). */
3160
- updatedAt: (0, import_yup37.number)().optional().label("Consent updated at"),
4431
+ updatedAt: (0, import_yup53.number)().optional().label("Consent updated at"),
3161
4432
  /** Where the consent change originated, e.g. `onboarding`, `unsubscribe`, `settings`. */
3162
- source: (0, import_yup37.string)().optional().label("Consent source"),
4433
+ source: (0, import_yup53.string)().optional().label("Consent source"),
3163
4434
  /**
3164
4435
  * How often the user receives job-alert emails. `off` disables job alerts
3165
4436
  * only; other marketing stays governed by `subscribed`. Unset means the
3166
4437
  * user has not chosen a cadence yet.
3167
4438
  */
3168
- jobAlertFrequency: (0, import_yup37.string)().oneOf(SupportedJobAlertFrequencies).optional().label("Job alert frequency")
4439
+ jobAlertFrequency: (0, import_yup53.string)().oneOf(SupportedJobAlertFrequencies).optional().label("Job alert frequency")
3169
4440
  }).default({ subscribed: false }).label("Marketing Consent"),
3170
4441
  /**
3171
4442
  * Per-channel toggles for NOTIFICATION email — mail about activity on the
@@ -3180,31 +4451,142 @@ var UserSchema = (0, import_yup37.object)({
3180
4451
  *
3181
4452
  * Transactional mail (magic-link, verification) ignores this object entirely.
3182
4453
  */
3183
- notificationPrefs: (0, import_yup37.object)({
3184
- groupActivity: (0, import_yup37.boolean)().default(true).label("Group activity email"),
3185
- jobActivity: (0, import_yup37.boolean)().default(true).label("Job activity email"),
3186
- applicationUpdates: (0, import_yup37.boolean)().default(true).label("Application update email"),
3187
- productUpdates: (0, import_yup37.boolean)().default(false).label("Product update email")
4454
+ notificationPrefs: (0, import_yup53.object)({
4455
+ groupActivity: (0, import_yup53.boolean)().default(true).label("Group activity email"),
4456
+ jobActivity: (0, import_yup53.boolean)().default(true).label("Job activity email"),
4457
+ applicationUpdates: (0, import_yup53.boolean)().default(true).label("Application update email"),
4458
+ productUpdates: (0, import_yup53.boolean)().default(false).label("Product update email")
3188
4459
  }).default({
3189
4460
  groupActivity: true,
3190
4461
  jobActivity: true,
3191
4462
  applicationUpdates: true,
3192
4463
  productUpdates: false
3193
4464
  }).label("Notification Preferences"),
4465
+ /**
4466
+ * Consent to receive WEB PUSH notifications.
4467
+ *
4468
+ * Push is a TRANSPORT, not a category: it carries the same notifications
4469
+ * `notificationPrefs` already governs, delivered to a device instead of an
4470
+ * inbox. That is why no push value is added to `NotificationType` — the user
4471
+ * chooses whether push is used at all, and WHICH kinds are worth pushing
4472
+ * stays a product decision.
4473
+ *
4474
+ * Opt-IN, and it must not be folded into `notificationPrefs`: those are
4475
+ * opt-OUT and read `!== false`, so an absent value means ENABLED. Applied to
4476
+ * push, every user who predates this field would read as having granted
4477
+ * permission they were never asked for. This mirrors `marketingConsent`
4478
+ * instead, which is the opt-in shape in this file, down to the provenance
4479
+ * fields.
4480
+ *
4481
+ * Consent alone never delivers anything: a push send also needs a live
4482
+ * subscription (see `PushSubscriptionSchema`). This flag is the user's
4483
+ * intent, the subscription is the browser grant, and both are required.
4484
+ */
4485
+ pushConsent: (0, import_yup53.object)({
4486
+ subscribed: (0, import_yup53.boolean)().default(false).label("Subscribed to push notifications"),
4487
+ /** Epoch ms of the last consent change (opt-in or opt-out). */
4488
+ updatedAt: (0, import_yup53.number)().optional().label("Push consent updated at"),
4489
+ /** Where the change originated, e.g. `onboarding`, `settings`, `prompt`. */
4490
+ source: (0, import_yup53.string)().optional().label("Push consent source")
4491
+ }).default({ subscribed: false }).label("Push Consent"),
3194
4492
  /**
3195
4493
  * Vector embedding of the user's profile for semantic/hybrid search.
3196
4494
  * Generated from a structured summary of skills, experience, education, etc.
3197
4495
  * Only generated for public profiles with sufficient completeness (≥60%).
3198
4496
  */
3199
- embedding: (0, import_yup37.object)({
3200
- vector: (0, import_yup37.array)((0, import_yup37.number)().required()).optional().label("Embedding vector"),
3201
- model: (0, import_yup37.string)().optional().label("Embedding model")
3202
- }).nullable().optional().label("Embedding")
4497
+ embedding: (0, import_yup53.object)({
4498
+ vector: (0, import_yup53.array)((0, import_yup53.number)().required()).optional().label("Embedding vector"),
4499
+ model: (0, import_yup53.string)().optional().label("Embedding model")
4500
+ }).nullable().optional().label("Embedding"),
4501
+ /**
4502
+ * Epoch ms the account was created, assigned by the storing service.
4503
+ *
4504
+ * Declared here, matching `GroupSchema`, because it is not merely stored: all
4505
+ * 103,608 user documents carry it, `UserFilterSchema` filters on it
4506
+ * (`createdAtWithinDays` / `createdAtOlderThanDays`), and the admin grid sorts
4507
+ * and displays it. It was absent, so a consumer deriving its row type from
4508
+ * this schema could not name the column it renders, and this object is
4509
+ * `.noUnknown().strict()` — an undeclared key is stripped on validation.
4510
+ *
4511
+ * `updatedAt` is deliberately NOT added alongside it: nothing derives from it
4512
+ * yet, and an optional field is only safe to add when something needs it.
4513
+ */
4514
+ createdAt: (0, import_yup53.number)().optional().label("Created At")
3203
4515
  }).concat(UserGeneralDetailSchema).concat(UserJobPreferencesSchema).concat(UserRecruiterProfileSchema).concat(UserCoordinatorProfileSchema).noUnknown().strict().label("User Schema");
3204
4516
 
4517
+ // src/user/push-subscription.schema.ts
4518
+ var import_yup54 = require("yup");
4519
+ var PushSubscriptionSchema = (0, import_yup54.object)({
4520
+ /**
4521
+ * The user this subscription belongs to, by the `shortId` every service
4522
+ * keys a user on, matching `UserSchema.shortId`.
4523
+ *
4524
+ * NOT the Better Auth account id. That id is meaningful only inside the auth
4525
+ * service, and owning these rows by one would force the service that sends
4526
+ * push to translate an id it has no other use for. The signed-in browser
4527
+ * gets its own shortId straight off the JWT claim.
4528
+ *
4529
+ * Named `userShortId` rather than `shortId` because a stored row has a
4530
+ * `shortId` of its OWN (see `PushSubscriptionRecord`); one field cannot be
4531
+ * both the row's id and its owner's.
4532
+ */
4533
+ userShortId: (0, import_yup54.string)().required().label("User Short ID"),
4534
+ /**
4535
+ * The push service URL the browser issued. Vendor-specific (FCM, Mozilla,
4536
+ * Apple) and effectively unbounded in length, so it carries no length cap.
4537
+ *
4538
+ * This is the IDENTITY of a subscription: it is what a re-subscribing
4539
+ * browser sends back unchanged, and what a 404/410 response identifies as
4540
+ * dead. Upserts and deletes both key on it.
4541
+ */
4542
+ endpoint: (0, import_yup54.string)().required().label("Push Endpoint"),
4543
+ /**
4544
+ * Encryption material from the browser. Web Push payloads are encrypted to
4545
+ * these keys, so a row missing either one cannot be delivered to at all.
4546
+ */
4547
+ keys: (0, import_yup54.object)({
4548
+ /** Client public key, base64url. */
4549
+ p256dh: (0, import_yup54.string)().required().label("P256DH Key"),
4550
+ /** Client auth secret, base64url. */
4551
+ auth: (0, import_yup54.string)().required().label("Auth Secret")
4552
+ }).required().label("Push Keys"),
4553
+ /**
4554
+ * Epoch ms the push service says the subscription expires, when it says
4555
+ * anything at all. Usually absent — most browsers return null — so this is
4556
+ * informational and never the basis for pruning. A 404/410 on send is the
4557
+ * only reliable death signal.
4558
+ */
4559
+ expirationTime: (0, import_yup54.number)().nullable().optional().label("Expiration Time"),
4560
+ /**
4561
+ * The subscribing browser's user agent, kept so a user can recognise their
4562
+ * own devices in a "where you receive notifications" list. Display only:
4563
+ * never parsed, and never used to decide delivery.
4564
+ */
4565
+ userAgent: (0, import_yup54.string)().optional().label("User Agent"),
4566
+ /** Epoch ms the subscription was first registered. */
4567
+ createdAt: (0, import_yup54.number)().optional().label("Created At"),
4568
+ /**
4569
+ * Epoch ms this subscription was last confirmed live, refreshed when the
4570
+ * browser re-registers it. A subscription that stops being refreshed is a
4571
+ * device that stopped visiting, which is useful for pruning rows that never
4572
+ * fail loudly enough to be caught by a 410.
4573
+ */
4574
+ lastSeenAt: (0, import_yup54.number)().optional().label("Last Seen At"),
4575
+ /**
4576
+ * Epoch ms the owner explicitly signed out on this device, or absent.
4577
+ *
4578
+ * A PAUSE, not a removal: the sender skips flagged rows, so a shared device
4579
+ * stops receiving the previous user's notifications, while the same user
4580
+ * signing back in clears the flag with no prompt. A flag rather than a delete
4581
+ * so that a MISSING row keeps one meaning to the browser ("stop, this device
4582
+ * was removed"), which it acts on by dropping its own subscription.
4583
+ */
4584
+ signedOutAt: (0, import_yup54.number)().nullable().optional().label("Signed Out At")
4585
+ }).noUnknown().strict().label("Push Subscription Schema");
4586
+
3205
4587
  // src/user/user-completeness.schema.ts
3206
- var import_yup38 = require("yup");
3207
- var UserCompletenessSchema = (0, import_yup38.object)({
4588
+ var import_yup55 = require("yup");
4589
+ var UserCompletenessSchema = (0, import_yup55.object)({
3208
4590
  additionalInfo: CompletenessScoreSchema(0).label("Additional Info"),
3209
4591
  // Optional
3210
4592
  certifications: CompletenessScoreSchema(0).label("Certifications"),
@@ -3229,22 +4611,29 @@ var StudentCompletenessSchema = UserCompletenessSchema.omit([
3229
4611
  0 && (module.exports = {
3230
4612
  AnswerChoiceType,
3231
4613
  ApplicationReceivePreference,
3232
- BindingKind,
3233
- BindingSchema,
4614
+ BillingPeriod,
4615
+ BillingSubscriptionSchema,
4616
+ BillingTransactionSchema,
3234
4617
  CAMPAIGN_MAX_RECIPIENTS_CEILING,
3235
- CampaignAnchorField,
4618
+ CONSUME_MAX_UNITS,
4619
+ CampaignDataSourceSchema,
3236
4620
  CampaignEndKind,
3237
4621
  CampaignIntervalUnit,
3238
4622
  CampaignSchema,
3239
4623
  CampaignStatus,
4624
+ CampaignStatusTransitions,
3240
4625
  CampaignTriggerKind,
3241
4626
  CampaignTriggerSchema,
3242
4627
  ChoiceQuestionSchema,
4628
+ CityFilterSchema,
4629
+ CitySchema,
3243
4630
  Common,
3244
4631
  CompensationType,
3245
4632
  CompletenessScoreSchema,
3246
4633
  ContactTypes,
3247
4634
  CoordinatorPageLinkSchema,
4635
+ CountryFilterSchema,
4636
+ CountrySchema,
3248
4637
  DISPLAY_DATE_FORMAT,
3249
4638
  DISPLAY_DATE_FORMAT_SHORT,
3250
4639
  DateStringSchema,
@@ -3258,12 +4647,29 @@ var StudentCompletenessSchema = UserCompletenessSchema.omit([
3258
4647
  EducationSchema,
3259
4648
  EmployeeCount,
3260
4649
  EmploymentType,
4650
+ EntitlementConsumeSchema,
4651
+ EntitlementDenialReason,
4652
+ EntitlementDenialSchema,
4653
+ EntitlingSubscriptionStatuses,
3261
4654
  ExperienceLevel,
3262
4655
  FEEDBACK_MESSAGE_MAX,
4656
+ FeatureEntitlementSchema,
4657
+ FeatureKey,
4658
+ FeedbackFilterSchema,
3263
4659
  FeedbackSchema,
3264
4660
  FeedbackStatus,
3265
4661
  FeedbackType,
4662
+ GEO_COLLECTIONS,
4663
+ GEO_SEARCH_MAX,
4664
+ GRANT_MAX_LIMIT,
3266
4665
  GeneraDetailFields,
4666
+ GeoKind,
4667
+ GeoSyncPhase,
4668
+ GeoSyncRunSchema,
4669
+ GeoSyncStatus,
4670
+ GeoSyncTrigger,
4671
+ GrantType,
4672
+ GroupFilterSchema,
3267
4673
  GroupManagedBy,
3268
4674
  GroupMembershipSchema,
3269
4675
  GroupMembershipStatus,
@@ -3275,31 +4681,41 @@ var StudentCompletenessSchema = UserCompletenessSchema.omit([
3275
4681
  JOB_TAXONOMY,
3276
4682
  JobAlertFrequency,
3277
4683
  JobCategory,
4684
+ JobFilterSchema,
3278
4685
  JobIndustry,
3279
4686
  JobSchema,
3280
4687
  JobSearchUrgency,
3281
4688
  JobStatus,
3282
4689
  JobSubCategory,
3283
- ListFilterBranch,
3284
- ListFilterBranchSchema,
3285
- ListFilterSchema,
4690
+ LanguageFilterSchema,
3286
4691
  LocationSchema,
3287
4692
  MIN_SALARY_LOWER_BOUND,
3288
4693
  MIN_SALARY_UPPER_BOUND,
4694
+ MailingListFilterSchema,
3289
4695
  MailingListSchema,
3290
- NotificationChannel,
4696
+ MoneySchema,
4697
+ NotificationType,
3291
4698
  OccurrenceStatus,
3292
4699
  PREFILL_MIRRORED_KEYS,
3293
4700
  PRIVATE_PROFILE_FIELDS,
4701
+ PaddleEnvironment,
4702
+ PaddlePriceIdsSchema,
4703
+ PageFilterSchema,
3294
4704
  PageSchema,
3295
4705
  PageStatus,
3296
4706
  PageType,
3297
4707
  PaginationSchema,
4708
+ PlanGrantSchema,
4709
+ PlanKind,
4710
+ PlanSchema,
4711
+ PlanStatus,
3298
4712
  PostSchema,
3299
4713
  PostStatus,
3300
4714
  ProficiencyLevel,
4715
+ PushSubscriptionSchema,
3301
4716
  QuestionSchema,
3302
4717
  QuestionType,
4718
+ QuotaPeriod,
3303
4719
  ReadAndAcknowledgeQuestionSchema,
3304
4720
  RecruiterPageLinkSchema,
3305
4721
  ReferralSource,
@@ -3311,15 +4727,18 @@ var StudentCompletenessSchema = UserCompletenessSchema.omit([
3311
4727
  SavedSearchSchema,
3312
4728
  SavedSearchStatus,
3313
4729
  SignupContextSource,
4730
+ SkillFilterSchema,
3314
4731
  SkillSchema,
3315
4732
  SocialAccount,
3316
4733
  SocialAccountSchema,
4734
+ StateFilterSchema,
4735
+ StateSchema,
3317
4736
  StudentCompletenessSchema,
3318
4737
  StudyType,
4738
+ SubscriptionStatus,
3319
4739
  SupportedAnswerChoiceTypes,
3320
4740
  SupportedApplicationReceivePreferences,
3321
- SupportedBindingKinds,
3322
- SupportedCampaignAnchorFields,
4741
+ SupportedBillingPeriods,
3323
4742
  SupportedCampaignEndKinds,
3324
4743
  SupportedCampaignIntervalUnits,
3325
4744
  SupportedCampaignStatuses,
@@ -3329,22 +4748,37 @@ var StudentCompletenessSchema = UserCompletenessSchema.omit([
3329
4748
  SupportedEducationLevels,
3330
4749
  SupportedEmployeeCounts,
3331
4750
  SupportedEmploymentTypes,
4751
+ SupportedEntitlementDenialReasons,
3332
4752
  SupportedExperienceLevels,
4753
+ SupportedFeatureKeys,
3333
4754
  SupportedFeedbackStatuses,
3334
4755
  SupportedFeedbackTypes,
4756
+ SupportedGeoKinds,
4757
+ SupportedGeoSyncPhases,
4758
+ SupportedGeoSyncStatuses,
4759
+ SupportedGeoSyncTriggers,
4760
+ SupportedGrantTypes,
4761
+ SupportedGroupManagedBy,
4762
+ SupportedGroupMembershipStatuses,
4763
+ SupportedGroupStatuses,
4764
+ SupportedGroupVisibilities,
3335
4765
  SupportedJobAlertFrequencies,
3336
4766
  SupportedJobCategories,
3337
4767
  SupportedJobIndustries,
3338
4768
  SupportedJobSearchUrgencies,
3339
4769
  SupportedJobStatuses,
3340
4770
  SupportedJobSubCategories,
3341
- SupportedNotificationChannels,
4771
+ SupportedNotificationTypes,
3342
4772
  SupportedOccurrenceStatuses,
4773
+ SupportedPaddleEnvironments,
3343
4774
  SupportedPageStatuses,
3344
4775
  SupportedPageTypes,
4776
+ SupportedPlanKinds,
4777
+ SupportedPlanStatuses,
3345
4778
  SupportedPostStatuses,
3346
4779
  SupportedProficiencyLevels,
3347
4780
  SupportedQuestionTypes,
4781
+ SupportedQuotaPeriods,
3348
4782
  SupportedReferralSources,
3349
4783
  SupportedReportReasons,
3350
4784
  SupportedResourceTypes,
@@ -3353,17 +4787,26 @@ var StudentCompletenessSchema = UserCompletenessSchema.omit([
3353
4787
  SupportedSignupContextSources,
3354
4788
  SupportedSocialAccounts,
3355
4789
  SupportedStudyTypes,
4790
+ SupportedSubscriptionStatuses,
4791
+ SupportedTemplateVariableBindingKinds,
4792
+ SupportedTransactionStatuses,
3356
4793
  SupportedUserProfileVisibilities,
3357
4794
  SupportedUserRoles,
3358
4795
  SupportedUserStatuses,
3359
4796
  SupportedWorkModes,
3360
4797
  TAXONOMY_LABELS,
4798
+ TemplateVariableBindingKind,
4799
+ TemplateVariableBindingSchema,
4800
+ TerminalCampaignStatuses,
4801
+ TerminalGeoSyncStatuses,
3361
4802
  TermsAcceptedSchema,
4803
+ TransactionStatus,
3362
4804
  UserAdditionalInfoSchema,
3363
4805
  UserCertificationSchema,
3364
4806
  UserCompletenessSchema,
3365
4807
  UserCoordinatorProfileSchema,
3366
4808
  UserDetailType,
4809
+ UserFilterSchema,
3367
4810
  UserGeneralDetailSchema,
3368
4811
  UserIdAndCreatedAtSchema,
3369
4812
  UserInterestSchema,
@@ -3378,12 +4821,24 @@ var StudentCompletenessSchema = UserCompletenessSchema.omit([
3378
4821
  UserStatus,
3379
4822
  WorkExperienceSchema,
3380
4823
  WorkMode,
4824
+ canTransitionCampaign,
3381
4825
  categoryOfIndustry,
3382
4826
  categoryOfSubCategory,
4827
+ codeTokens,
4828
+ conjunction,
4829
+ currencyBound,
3383
4830
  dateString,
4831
+ days,
3384
4832
  deriveIndustry,
4833
+ enumFacet,
4834
+ epoch,
3385
4835
  getSchemaByQuestion,
3386
4836
  industriesOfCategory,
3387
4837
  industryOfSubCategory,
3388
- subCategoriesOfIndustry
4838
+ months,
4839
+ percent,
4840
+ qualified,
4841
+ rejectUnknownKeys,
4842
+ subCategoriesOfIndustry,
4843
+ tokens
3389
4844
  });