@thejob/schema 2.1.5 → 2.1.7

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
@@ -594,6 +594,26 @@ var PageSchema = (0, import_yup12.object)().shape({
594
594
  var import_yup13 = require("yup");
595
595
  var SkillSchema = (0, import_yup13.object)({
596
596
  name: (0, import_yup13.string)().trim().required().label("Skill Name"),
597
+ /**
598
+ * The taxonomy concept this skill resolved to, assigned by thejob-taxonomy-service
599
+ * via `POST /normalize/batch`. Stable slug, e.g. "power-bi".
600
+ *
601
+ * Normalization is ADDITIVE and never authoritative over `name`: the raw string a
602
+ * job was posted with is always preserved, and these two fields are written
603
+ * alongside it. That is what makes a wrong match, a taxonomy re-run, or a service
604
+ * outage recoverable rather than a data loss.
605
+ *
606
+ * Optional and nullable because they genuinely are absent in two normal cases,
607
+ * neither of which is an error:
608
+ * - the term did not resolve (~22% of mentions are a long tail the taxonomy has
609
+ * no concept for yet), and the raw `name` stands alone;
610
+ * - the document predates normalization and has not been re-processed.
611
+ *
612
+ * Consumers must therefore read `conceptName ?? name`, never `conceptName` alone.
613
+ */
614
+ conceptId: (0, import_yup13.string)().optional().nullable().label("Concept ID"),
615
+ /** Canonical display name for `conceptId`, e.g. "Power BI". See `conceptId`. */
616
+ conceptName: (0, import_yup13.string)().optional().nullable().label("Concept Name"),
597
617
  logo: (0, import_yup13.object)({
598
618
  light: (0, import_yup13.string)().required().label("Light Logo"),
599
619
  dark: (0, import_yup13.string)().optional().nullable().label("Dark Logo")
@@ -799,7 +819,15 @@ var JobSchema = (0, import_yup18.object)({
799
819
  designation: (0, import_yup18.string)().optional().nullable().label("Designation"),
800
820
  employmentType: (0, import_yup18.string)().required().label("Employment type"),
801
821
  workMode: (0, import_yup18.string)().required().label("Work mode"),
802
- skills: (0, import_yup18.array)().of(SkillSchema.pick(["name", "logo"]).required().label("Skill")).nullable().optional().label("Skills"),
822
+ /**
823
+ * `conceptId`/`conceptName` carry the taxonomy-service normalization. They are
824
+ * picked here (not just `name`/`logo`) because SkillSchema is `.noUnknown()`:
825
+ * an unpicked field that is actually PRESENT fails validation, so omitting them
826
+ * would reject every normalized job.
827
+ */
828
+ skills: (0, import_yup18.array)().of(
829
+ SkillSchema.pick(["name", "logo", "conceptId", "conceptName"]).required().label("Skill")
830
+ ).nullable().optional().label("Skills"),
803
831
  bookmarks: (0, import_yup18.array)().of(UserIdAndCreatedAtSchema).optional().label("Bookmarks"),
804
832
  embedding: (0, import_yup18.object)({
805
833
  vector: (0, import_yup18.array)((0, import_yup18.number)().required()).optional().label("Embedding vector"),
@@ -2541,6 +2569,25 @@ var UserGeneralDetailSchema = (0, import_yup33.object)({
2541
2569
  emailVerified: (0, import_yup33.string)().nullable().optional().label("Email Verified"),
2542
2570
  mobileVerified: (0, import_yup33.string)().nullable().optional().label("Mobile Verified"),
2543
2571
  experienceLevel: (0, import_yup33.string)().oneOf(SupportedExperienceLevels).required().label("Experience level"),
2572
+ /**
2573
+ * The user's HIGHEST qualification, e.g. `bachelor`.
2574
+ *
2575
+ * The counterpart of `JobSchema.educationLevel` (what a listing requires), so
2576
+ * matching can compare the two directly. Nothing on the user side answered
2577
+ * that question before: `educations[].studyType` is the MODE of study
2578
+ * (`full_time`, `online`) and says nothing about attainment.
2579
+ *
2580
+ * Deliberately top-level and singular, alongside `experienceLevel`, rather
2581
+ * than per `educations` entry: what matching needs is one fact about the
2582
+ * person, and deriving it by ranking every entry would make the answer depend
2583
+ * on how completely a user filled in their history.
2584
+ *
2585
+ * Optional, unlike `experienceLevel`: every existing user predates it. An
2586
+ * absent value means "level unknown", which must leave matching
2587
+ * UNCONSTRAINED on education rather than filtering the user out — the same
2588
+ * opt-out reasoning `notificationPrefs` uses.
2589
+ */
2590
+ educationLevel: (0, import_yup33.string)().oneOf(SupportedEducationLevels).optional().label("Education level"),
2544
2591
  location: LocationSchema.required().label("Location"),
2545
2592
  /**
2546
2593
  * Where the account was created, captured once and never rewritten.
package/dist/index.d.cts CHANGED
@@ -437,6 +437,8 @@ declare const JobSchema: ObjectSchema<{
437
437
  employmentType: string;
438
438
  workMode: string;
439
439
  skills: {
440
+ conceptId?: string | null | undefined;
441
+ conceptName?: string | null | undefined;
440
442
  name: string;
441
443
  logo: {
442
444
  dark?: string | null | undefined;
@@ -1736,6 +1738,8 @@ declare const FeedbackSchema: yup.ObjectSchema<{
1736
1738
 
1737
1739
  declare const SkillSchema: yup.ObjectSchema<{
1738
1740
  name: string;
1741
+ conceptId: string | null | undefined;
1742
+ conceptName: string | null | undefined;
1739
1743
  logo: {
1740
1744
  dark?: string | null | undefined;
1741
1745
  light: string;
@@ -1749,6 +1753,8 @@ declare const SkillSchema: yup.ObjectSchema<{
1749
1753
  updatedAt: number | undefined;
1750
1754
  }, yup.AnyObject, {
1751
1755
  name: undefined;
1756
+ conceptId: undefined;
1757
+ conceptName: undefined;
1752
1758
  logo: null;
1753
1759
  tags: "";
1754
1760
  shortId: undefined;
@@ -1937,6 +1943,7 @@ declare const UserSchema: yup.ObjectSchema<{
1937
1943
  emailVerified: string | null | undefined;
1938
1944
  mobileVerified: string | null | undefined;
1939
1945
  experienceLevel: NonNullable<ExperienceLevel | undefined>;
1946
+ educationLevel: EducationLevel | undefined;
1940
1947
  location: {
1941
1948
  placeId?: string | null | undefined;
1942
1949
  state?: string | null | undefined;
@@ -2144,6 +2151,7 @@ declare const UserSchema: yup.ObjectSchema<{
2144
2151
  emailVerified: undefined;
2145
2152
  mobileVerified: undefined;
2146
2153
  experienceLevel: undefined;
2154
+ educationLevel: undefined;
2147
2155
  location: {
2148
2156
  placeId: undefined;
2149
2157
  country: undefined;
@@ -2196,6 +2204,7 @@ declare const UserGeneralDetailSchema: yup.ObjectSchema<{
2196
2204
  emailVerified: string | null | undefined;
2197
2205
  mobileVerified: string | null | undefined;
2198
2206
  experienceLevel: NonNullable<ExperienceLevel | undefined>;
2207
+ educationLevel: EducationLevel | undefined;
2199
2208
  location: {
2200
2209
  placeId?: string | null | undefined;
2201
2210
  state?: string | null | undefined;
@@ -2230,6 +2239,7 @@ declare const UserGeneralDetailSchema: yup.ObjectSchema<{
2230
2239
  emailVerified: undefined;
2231
2240
  mobileVerified: undefined;
2232
2241
  experienceLevel: undefined;
2242
+ educationLevel: undefined;
2233
2243
  location: {
2234
2244
  placeId: undefined;
2235
2245
  country: undefined;
package/dist/index.d.ts CHANGED
@@ -437,6 +437,8 @@ declare const JobSchema: ObjectSchema<{
437
437
  employmentType: string;
438
438
  workMode: string;
439
439
  skills: {
440
+ conceptId?: string | null | undefined;
441
+ conceptName?: string | null | undefined;
440
442
  name: string;
441
443
  logo: {
442
444
  dark?: string | null | undefined;
@@ -1736,6 +1738,8 @@ declare const FeedbackSchema: yup.ObjectSchema<{
1736
1738
 
1737
1739
  declare const SkillSchema: yup.ObjectSchema<{
1738
1740
  name: string;
1741
+ conceptId: string | null | undefined;
1742
+ conceptName: string | null | undefined;
1739
1743
  logo: {
1740
1744
  dark?: string | null | undefined;
1741
1745
  light: string;
@@ -1749,6 +1753,8 @@ declare const SkillSchema: yup.ObjectSchema<{
1749
1753
  updatedAt: number | undefined;
1750
1754
  }, yup.AnyObject, {
1751
1755
  name: undefined;
1756
+ conceptId: undefined;
1757
+ conceptName: undefined;
1752
1758
  logo: null;
1753
1759
  tags: "";
1754
1760
  shortId: undefined;
@@ -1937,6 +1943,7 @@ declare const UserSchema: yup.ObjectSchema<{
1937
1943
  emailVerified: string | null | undefined;
1938
1944
  mobileVerified: string | null | undefined;
1939
1945
  experienceLevel: NonNullable<ExperienceLevel | undefined>;
1946
+ educationLevel: EducationLevel | undefined;
1940
1947
  location: {
1941
1948
  placeId?: string | null | undefined;
1942
1949
  state?: string | null | undefined;
@@ -2144,6 +2151,7 @@ declare const UserSchema: yup.ObjectSchema<{
2144
2151
  emailVerified: undefined;
2145
2152
  mobileVerified: undefined;
2146
2153
  experienceLevel: undefined;
2154
+ educationLevel: undefined;
2147
2155
  location: {
2148
2156
  placeId: undefined;
2149
2157
  country: undefined;
@@ -2196,6 +2204,7 @@ declare const UserGeneralDetailSchema: yup.ObjectSchema<{
2196
2204
  emailVerified: string | null | undefined;
2197
2205
  mobileVerified: string | null | undefined;
2198
2206
  experienceLevel: NonNullable<ExperienceLevel | undefined>;
2207
+ educationLevel: EducationLevel | undefined;
2199
2208
  location: {
2200
2209
  placeId?: string | null | undefined;
2201
2210
  state?: string | null | undefined;
@@ -2230,6 +2239,7 @@ declare const UserGeneralDetailSchema: yup.ObjectSchema<{
2230
2239
  emailVerified: undefined;
2231
2240
  mobileVerified: undefined;
2232
2241
  experienceLevel: undefined;
2242
+ educationLevel: undefined;
2233
2243
  location: {
2234
2244
  placeId: undefined;
2235
2245
  country: undefined;
package/dist/index.js CHANGED
@@ -417,6 +417,26 @@ var PageSchema = object10().shape({
417
417
  import { array as array6, object as object11, string as string8 } from "yup";
418
418
  var SkillSchema = object11({
419
419
  name: string8().trim().required().label("Skill Name"),
420
+ /**
421
+ * The taxonomy concept this skill resolved to, assigned by thejob-taxonomy-service
422
+ * via `POST /normalize/batch`. Stable slug, e.g. "power-bi".
423
+ *
424
+ * Normalization is ADDITIVE and never authoritative over `name`: the raw string a
425
+ * job was posted with is always preserved, and these two fields are written
426
+ * alongside it. That is what makes a wrong match, a taxonomy re-run, or a service
427
+ * outage recoverable rather than a data loss.
428
+ *
429
+ * Optional and nullable because they genuinely are absent in two normal cases,
430
+ * neither of which is an error:
431
+ * - the term did not resolve (~22% of mentions are a long tail the taxonomy has
432
+ * no concept for yet), and the raw `name` stands alone;
433
+ * - the document predates normalization and has not been re-processed.
434
+ *
435
+ * Consumers must therefore read `conceptName ?? name`, never `conceptName` alone.
436
+ */
437
+ conceptId: string8().optional().nullable().label("Concept ID"),
438
+ /** Canonical display name for `conceptId`, e.g. "Power BI". See `conceptId`. */
439
+ conceptName: string8().optional().nullable().label("Concept Name"),
420
440
  logo: object11({
421
441
  light: string8().required().label("Light Logo"),
422
442
  dark: string8().optional().nullable().label("Dark Logo")
@@ -622,7 +642,15 @@ var JobSchema = object16({
622
642
  designation: string13().optional().nullable().label("Designation"),
623
643
  employmentType: string13().required().label("Employment type"),
624
644
  workMode: string13().required().label("Work mode"),
625
- skills: array8().of(SkillSchema.pick(["name", "logo"]).required().label("Skill")).nullable().optional().label("Skills"),
645
+ /**
646
+ * `conceptId`/`conceptName` carry the taxonomy-service normalization. They are
647
+ * picked here (not just `name`/`logo`) because SkillSchema is `.noUnknown()`:
648
+ * an unpicked field that is actually PRESENT fails validation, so omitting them
649
+ * would reject every normalized job.
650
+ */
651
+ skills: array8().of(
652
+ SkillSchema.pick(["name", "logo", "conceptId", "conceptName"]).required().label("Skill")
653
+ ).nullable().optional().label("Skills"),
626
654
  bookmarks: array8().of(UserIdAndCreatedAtSchema).optional().label("Bookmarks"),
627
655
  embedding: object16({
628
656
  vector: array8(number5().required()).optional().label("Embedding vector"),
@@ -2364,6 +2392,25 @@ var UserGeneralDetailSchema = object30({
2364
2392
  emailVerified: string28().nullable().optional().label("Email Verified"),
2365
2393
  mobileVerified: string28().nullable().optional().label("Mobile Verified"),
2366
2394
  experienceLevel: string28().oneOf(SupportedExperienceLevels).required().label("Experience level"),
2395
+ /**
2396
+ * The user's HIGHEST qualification, e.g. `bachelor`.
2397
+ *
2398
+ * The counterpart of `JobSchema.educationLevel` (what a listing requires), so
2399
+ * matching can compare the two directly. Nothing on the user side answered
2400
+ * that question before: `educations[].studyType` is the MODE of study
2401
+ * (`full_time`, `online`) and says nothing about attainment.
2402
+ *
2403
+ * Deliberately top-level and singular, alongside `experienceLevel`, rather
2404
+ * than per `educations` entry: what matching needs is one fact about the
2405
+ * person, and deriving it by ranking every entry would make the answer depend
2406
+ * on how completely a user filled in their history.
2407
+ *
2408
+ * Optional, unlike `experienceLevel`: every existing user predates it. An
2409
+ * absent value means "level unknown", which must leave matching
2410
+ * UNCONSTRAINED on education rather than filtering the user out — the same
2411
+ * opt-out reasoning `notificationPrefs` uses.
2412
+ */
2413
+ educationLevel: string28().oneOf(SupportedEducationLevels).optional().label("Education level"),
2367
2414
  location: LocationSchema.required().label("Location"),
2368
2415
  /**
2369
2416
  * Where the account was created, captured once and never rewritten.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thejob/schema",
3
- "version": "2.1.5",
3
+ "version": "2.1.7",
4
4
  "type": "module",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",
@@ -69,8 +69,18 @@ export const JobSchema = object({
69
69
 
70
70
  workMode: string().required().label("Work mode"),
71
71
 
72
+ /**
73
+ * `conceptId`/`conceptName` carry the taxonomy-service normalization. They are
74
+ * picked here (not just `name`/`logo`) because SkillSchema is `.noUnknown()`:
75
+ * an unpicked field that is actually PRESENT fails validation, so omitting them
76
+ * would reject every normalized job.
77
+ */
72
78
  skills: array()
73
- .of(SkillSchema.pick(["name", "logo"]).required().label("Skill"))
79
+ .of(
80
+ SkillSchema.pick(["name", "logo", "conceptId", "conceptName"])
81
+ .required()
82
+ .label("Skill"),
83
+ )
74
84
  .nullable()
75
85
  .optional()
76
86
  .label("Skills"),
@@ -3,6 +3,29 @@ import { DbDefaultSchema } from "../common/common.schema.js";
3
3
 
4
4
  export const SkillSchema = object({
5
5
  name: string().trim().required().label("Skill Name"),
6
+
7
+ /**
8
+ * The taxonomy concept this skill resolved to, assigned by thejob-taxonomy-service
9
+ * via `POST /normalize/batch`. Stable slug, e.g. "power-bi".
10
+ *
11
+ * Normalization is ADDITIVE and never authoritative over `name`: the raw string a
12
+ * job was posted with is always preserved, and these two fields are written
13
+ * alongside it. That is what makes a wrong match, a taxonomy re-run, or a service
14
+ * outage recoverable rather than a data loss.
15
+ *
16
+ * Optional and nullable because they genuinely are absent in two normal cases,
17
+ * neither of which is an error:
18
+ * - the term did not resolve (~22% of mentions are a long tail the taxonomy has
19
+ * no concept for yet), and the raw `name` stands alone;
20
+ * - the document predates normalization and has not been re-processed.
21
+ *
22
+ * Consumers must therefore read `conceptName ?? name`, never `conceptName` alone.
23
+ */
24
+ conceptId: string().optional().nullable().label("Concept ID"),
25
+
26
+ /** Canonical display name for `conceptId`, e.g. "Power BI". See `conceptId`. */
27
+ conceptName: string().optional().nullable().label("Concept Name"),
28
+
6
29
  logo: object({
7
30
  light: string().required().label("Light Logo"),
8
31
  dark: string().optional().nullable().label("Dark Logo"),
@@ -1,5 +1,8 @@
1
1
  import { number, object, string } from "yup";
2
- import { SupportedExperienceLevels } from "../common/common.constant.js";
2
+ import {
3
+ SupportedEducationLevels,
4
+ SupportedExperienceLevels,
5
+ } from "../common/common.constant.js";
3
6
  import { LocationSchema } from "../location/location.schema.js";
4
7
  import {
5
8
  SupportedSignupContextSources,
@@ -30,6 +33,28 @@ export const UserGeneralDetailSchema = object({
30
33
  .oneOf(SupportedExperienceLevels)
31
34
  .required()
32
35
  .label("Experience level"),
36
+ /**
37
+ * The user's HIGHEST qualification, e.g. `bachelor`.
38
+ *
39
+ * The counterpart of `JobSchema.educationLevel` (what a listing requires), so
40
+ * matching can compare the two directly. Nothing on the user side answered
41
+ * that question before: `educations[].studyType` is the MODE of study
42
+ * (`full_time`, `online`) and says nothing about attainment.
43
+ *
44
+ * Deliberately top-level and singular, alongside `experienceLevel`, rather
45
+ * than per `educations` entry: what matching needs is one fact about the
46
+ * person, and deriving it by ranking every entry would make the answer depend
47
+ * on how completely a user filled in their history.
48
+ *
49
+ * Optional, unlike `experienceLevel`: every existing user predates it. An
50
+ * absent value means "level unknown", which must leave matching
51
+ * UNCONSTRAINED on education rather than filtering the user out — the same
52
+ * opt-out reasoning `notificationPrefs` uses.
53
+ */
54
+ educationLevel: string()
55
+ .oneOf(SupportedEducationLevels)
56
+ .optional()
57
+ .label("Education level"),
33
58
  location: LocationSchema.required().label("Location"),
34
59
  /**
35
60
  * Where the account was created, captured once and never rewritten.