@cavuno/board 4.27.0 → 4.28.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.
Files changed (45) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/dist/{_spec-B_pBNVUd.d.mts → _spec-DQvmsp5o.d.mts} +102 -20
  3. package/dist/{_spec-B_pBNVUd.d.ts → _spec-DQvmsp5o.d.ts} +102 -20
  4. package/dist/{blog-DzGRUsj5.d.ts → blog-CUkvhn3c.d.ts} +1 -1
  5. package/dist/{blog-fhXRik6k.d.mts → blog-CvuObn9O.d.mts} +1 -1
  6. package/dist/{board-fVAXZ8T5.d.ts → board-CCG1pO6r.d.ts} +2 -2
  7. package/dist/{board-DEiEHcpe.d.mts → board-D6xOidUX.d.mts} +2 -2
  8. package/dist/{breadcrumbs-qE3CD--N.d.mts → breadcrumbs-BBvJ5gq-.d.mts} +2 -2
  9. package/dist/{breadcrumbs-DNaaP3xa.d.ts → breadcrumbs-D2Cwrju4.d.ts} +2 -2
  10. package/dist/browser/cavuno-board.global.min.js +1 -1
  11. package/dist/{chunk-DUTQOLOF.mjs → chunk-EHSB2VAJ.mjs} +4 -0
  12. package/dist/filters.d.mts +2 -2
  13. package/dist/filters.d.ts +2 -2
  14. package/dist/format.d.mts +4 -4
  15. package/dist/format.d.ts +4 -4
  16. package/dist/index.d.mts +91 -14
  17. package/dist/index.d.ts +91 -14
  18. package/dist/index.js +44 -4
  19. package/dist/index.mjs +41 -5
  20. package/dist/{jobs-R-h3A3kl.d.mts → jobs-CEIuahbz.d.ts} +9 -7
  21. package/dist/{jobs-BRysn2m0.d.ts → jobs-DjMcghv6.d.mts} +9 -7
  22. package/dist/{search-BlB690ma.d.mts → search-C0LiPXVb.d.mts} +1 -1
  23. package/dist/{search-BK_zHHNQ.d.ts → search-D-EjtZH3.d.ts} +1 -1
  24. package/dist/{seo-C7gMl6Y7.d.ts → seo-CjNGqZZH.d.ts} +1 -1
  25. package/dist/{seo-0mbZ7M_E.d.mts → seo-DCQp8V1c.d.mts} +1 -1
  26. package/dist/seo-files.d.mts +2 -2
  27. package/dist/seo-files.d.ts +2 -2
  28. package/dist/seo.d.mts +6 -6
  29. package/dist/seo.d.ts +6 -6
  30. package/dist/server.d.mts +6 -6
  31. package/dist/server.d.ts +6 -6
  32. package/dist/server.mjs +1 -1
  33. package/dist/sitemap.d.mts +6 -6
  34. package/dist/sitemap.d.ts +6 -6
  35. package/dist/sitemap.mjs +1 -1
  36. package/dist/suggest.d.mts +2 -2
  37. package/dist/suggest.d.ts +2 -2
  38. package/package.json +1 -1
  39. package/skills/cavuno-board-account/SKILL.md +27 -0
  40. package/skills/cavuno-board-errors/SKILL.md +1 -1
  41. package/skills/cavuno-board-filters/SKILL.md +90 -1
  42. package/skills/cavuno-board-format/SKILL.md +52 -0
  43. package/skills/cavuno-board-post-a-job/SKILL.md +24 -0
  44. package/skills/cavuno-board-salaries/SKILL.md +7 -0
  45. package/skills/manifest.json +2 -2
package/dist/index.js CHANGED
@@ -89,6 +89,9 @@ var BOARD_API_ERROR_CODES = [
89
89
  "categories_not_found",
90
90
  "skills_not_found",
91
91
  "places_not_found",
92
+ // Location search + `locationId` on job office locations
93
+ "locations_invalid_id",
94
+ "locations_unavailable",
92
95
  // Companies (public reads)
93
96
  "companies_not_found",
94
97
  "company_markets_not_found",
@@ -120,6 +123,7 @@ var BOARD_API_ERROR_CODES = [
120
123
  "candidate_handle_taken",
121
124
  "candidate_job_not_found",
122
125
  "candidate_notification_preference_paused",
126
+ "candidate_profile_required",
123
127
  "resume_invalid_file",
124
128
  "resume_upload_forbidden",
125
129
  // Applications
@@ -359,7 +363,7 @@ async function clearSession(storage) {
359
363
  }
360
364
 
361
365
  // src/version.ts
362
- var SDK_VERSION = "4.27.0";
366
+ var SDK_VERSION = "4.28.0";
363
367
 
364
368
  // src/client.ts
365
369
  function isRawBody(body) {
@@ -1234,6 +1238,37 @@ function jobsNamespace(client) {
1234
1238
  };
1235
1239
  }
1236
1240
 
1241
+ // src/namespaces/locations.ts
1242
+ function locationsNamespace(client) {
1243
+ return {
1244
+ /**
1245
+ * GET /locations/search — worldwide autocomplete over countries,
1246
+ * regions, cities, and localities, not only places the board's jobs already
1247
+ * use (that is `taxonomy.places.list({ q })`). Order is the contract: do
1248
+ * not re-sort. Complete a pick with `locations.resolve` using the same
1249
+ * session before sending its `id` as `locationId` later.
1250
+ *
1251
+ * @example
1252
+ * const session = crypto.randomUUID();
1253
+ * const { data } = await board.locations.search({ q: 'berl', session });
1254
+ */
1255
+ search(query, options) {
1256
+ return client.fetch("/locations/search", {
1257
+ ...options,
1258
+ query
1259
+ });
1260
+ },
1261
+ /** Complete a picked suggestion with the same search session. */
1262
+ resolve(input, options) {
1263
+ return client.fetch("/locations/resolve", {
1264
+ ...options,
1265
+ method: "POST",
1266
+ body: input
1267
+ });
1268
+ }
1269
+ };
1270
+ }
1271
+
1237
1272
  // src/namespaces/me.ts
1238
1273
  function meNamespace(client) {
1239
1274
  return {
@@ -1995,15 +2030,19 @@ function meNamespace(client) {
1995
2030
  /**
1996
2031
  * Pay for / publish a held draft. Select billing: a paid plan
1997
2032
  * returns `{ status: 'checkout', checkoutUrl }` (send the buyer there;
1998
- * the webhook publishes on payment); a free / bundle / subscription plan
1999
- * publishes immediately (`status: 'published'`); an invoice plan emails a
2000
- * Stripe invoice (`status: 'invoice_sent'`).
2033
+ * the webhook publishes on payment); a free plan publishes immediately
2034
+ * (`status: 'published'`) unless the board requires free-job approval
2035
+ * (`status: 'pending_approval'`); bundle, subscription, and member-credit
2036
+ * posts always publish immediately; an invoice plan emails a Stripe
2037
+ * invoice (`status: 'invoice_sent'`) or holds an on-issue post for
2038
+ * approval.
2001
2039
  *
2002
2040
  * @example
2003
2041
  * const r = await board.me.companies.jobs.checkout('acme', jobId, {
2004
2042
  * billing: { type: 'new', planId },
2005
2043
  * });
2006
2044
  * if (r.status === 'checkout') location.href = r.checkoutUrl!;
2045
+ * if (r.status === 'pending_approval') showAwaitingReview(r.jobId);
2007
2046
  */
2008
2047
  checkout(slug, id, body, options) {
2009
2048
  return client.fetch(
@@ -3665,6 +3704,7 @@ function createBoardClient(options) {
3665
3704
  password: passwordNamespace(client),
3666
3705
  taxonomy: taxonomyNamespace(client),
3667
3706
  search: searchNamespace(client),
3707
+ locations: locationsNamespace(client),
3668
3708
  redirects: redirectsNamespace(client),
3669
3709
  jobAlerts: jobAlertsNamespace(client),
3670
3710
  jobPosting: jobPostingNamespace(client),
package/dist/index.mjs CHANGED
@@ -21,7 +21,7 @@ import {
21
21
  isRateLimited,
22
22
  isUnauthorized,
23
23
  isValidationError
24
- } from "./chunk-DUTQOLOF.mjs";
24
+ } from "./chunk-EHSB2VAJ.mjs";
25
25
 
26
26
  // src/query.ts
27
27
  function toSearchParams(query) {
@@ -42,7 +42,7 @@ function toSearchParams(query) {
42
42
  }
43
43
 
44
44
  // src/version.ts
45
- var SDK_VERSION = "4.27.0";
45
+ var SDK_VERSION = "4.28.0";
46
46
 
47
47
  // src/client.ts
48
48
  function isRawBody(body) {
@@ -917,6 +917,37 @@ function jobsNamespace(client) {
917
917
  };
918
918
  }
919
919
 
920
+ // src/namespaces/locations.ts
921
+ function locationsNamespace(client) {
922
+ return {
923
+ /**
924
+ * GET /locations/search — worldwide autocomplete over countries,
925
+ * regions, cities, and localities, not only places the board's jobs already
926
+ * use (that is `taxonomy.places.list({ q })`). Order is the contract: do
927
+ * not re-sort. Complete a pick with `locations.resolve` using the same
928
+ * session before sending its `id` as `locationId` later.
929
+ *
930
+ * @example
931
+ * const session = crypto.randomUUID();
932
+ * const { data } = await board.locations.search({ q: 'berl', session });
933
+ */
934
+ search(query, options) {
935
+ return client.fetch("/locations/search", {
936
+ ...options,
937
+ query
938
+ });
939
+ },
940
+ /** Complete a picked suggestion with the same search session. */
941
+ resolve(input, options) {
942
+ return client.fetch("/locations/resolve", {
943
+ ...options,
944
+ method: "POST",
945
+ body: input
946
+ });
947
+ }
948
+ };
949
+ }
950
+
920
951
  // src/namespaces/me.ts
921
952
  function meNamespace(client) {
922
953
  return {
@@ -1678,15 +1709,19 @@ function meNamespace(client) {
1678
1709
  /**
1679
1710
  * Pay for / publish a held draft. Select billing: a paid plan
1680
1711
  * returns `{ status: 'checkout', checkoutUrl }` (send the buyer there;
1681
- * the webhook publishes on payment); a free / bundle / subscription plan
1682
- * publishes immediately (`status: 'published'`); an invoice plan emails a
1683
- * Stripe invoice (`status: 'invoice_sent'`).
1712
+ * the webhook publishes on payment); a free plan publishes immediately
1713
+ * (`status: 'published'`) unless the board requires free-job approval
1714
+ * (`status: 'pending_approval'`); bundle, subscription, and member-credit
1715
+ * posts always publish immediately; an invoice plan emails a Stripe
1716
+ * invoice (`status: 'invoice_sent'`) or holds an on-issue post for
1717
+ * approval.
1684
1718
  *
1685
1719
  * @example
1686
1720
  * const r = await board.me.companies.jobs.checkout('acme', jobId, {
1687
1721
  * billing: { type: 'new', planId },
1688
1722
  * });
1689
1723
  * if (r.status === 'checkout') location.href = r.checkoutUrl!;
1724
+ * if (r.status === 'pending_approval') showAwaitingReview(r.jobId);
1690
1725
  */
1691
1726
  checkout(slug, id, body, options) {
1692
1727
  return client.fetch(
@@ -3318,6 +3353,7 @@ function createBoardClient(options) {
3318
3353
  password: passwordNamespace(client),
3319
3354
  taxonomy: taxonomyNamespace(client),
3320
3355
  search: searchNamespace(client),
3356
+ locations: locationsNamespace(client),
3321
3357
  redirects: redirectsNamespace(client),
3322
3358
  jobAlerts: jobAlertsNamespace(client),
3323
3359
  jobPosting: jobPostingNamespace(client),
@@ -1,4 +1,4 @@
1
- import { S as Schemas } from './_spec-B_pBNVUd.mjs';
1
+ import { S as Schemas } from './_spec-DQvmsp5o.js';
2
2
 
3
3
  /**
4
4
  * Optional pagination metadata returned by Board API list operations.
@@ -119,11 +119,13 @@ type ObjectReferenceFilter = {
119
119
  key: string;
120
120
  recordIds: string[];
121
121
  };
122
- type GeneratedJobsSearchBody = Schemas['PublicSearchJobsBody'];
123
- type JobsSearchBody = Omit<GeneratedJobsSearchBody, 'filters'> & {
124
- filters?: NonNullable<GeneratedJobsSearchBody['filters']> & {
125
- customFields?: CustomFieldFilter[];
126
- };
127
- };
122
+ /**
123
+ * The `jobs.search` body, aliased from the spec like the response types.
124
+ * `filters.customFields` matches the job's own custom fields and
125
+ * `filters.companyCustomFields` the public profile fields of the job's
126
+ * company; both take {@link CustomFieldFilter} clauses (the tests pin that
127
+ * the spec shape and `CustomFieldFilter` stay identical).
128
+ */
129
+ type JobsSearchBody = Schemas['PublicSearchJobsBody'];
128
130
 
129
131
  export type { CustomFieldValues as C, EmploymentType as E, JobSort as J, ListEnvelope as L, ObjectReferenceFilter as O, PublicJob as P, RemoteOption as R, Seniority as S, CustomFieldFilter as a, JobCollectionChoiceQuery as b, JobsListQuery as c, JobCardListEnvelope as d, JobsSearchBody as e, JobCardSearchEnvelope as f, JobsSimilarQuery as g, SearchEnvelope as h, CustomFieldValue as i, CustomFilterValue as j, EducationRequirement as k, JobCatalogPagination as l, JobCollectionChoiceList as m, JobCollectionFieldDefinition as n, JobCompany as o, OfficeLocation as p, OffsetPagination as q, PublicJobCard as r, RelatedSearch as s, RemotePermit as t, RemoteTimezone as u, ResolvedJobCollectionField as v, StorefrontPagination as w };
@@ -1,4 +1,4 @@
1
- import { S as Schemas } from './_spec-B_pBNVUd.js';
1
+ import { S as Schemas } from './_spec-DQvmsp5o.mjs';
2
2
 
3
3
  /**
4
4
  * Optional pagination metadata returned by Board API list operations.
@@ -119,11 +119,13 @@ type ObjectReferenceFilter = {
119
119
  key: string;
120
120
  recordIds: string[];
121
121
  };
122
- type GeneratedJobsSearchBody = Schemas['PublicSearchJobsBody'];
123
- type JobsSearchBody = Omit<GeneratedJobsSearchBody, 'filters'> & {
124
- filters?: NonNullable<GeneratedJobsSearchBody['filters']> & {
125
- customFields?: CustomFieldFilter[];
126
- };
127
- };
122
+ /**
123
+ * The `jobs.search` body, aliased from the spec like the response types.
124
+ * `filters.customFields` matches the job's own custom fields and
125
+ * `filters.companyCustomFields` the public profile fields of the job's
126
+ * company; both take {@link CustomFieldFilter} clauses (the tests pin that
127
+ * the spec shape and `CustomFieldFilter` stay identical).
128
+ */
129
+ type JobsSearchBody = Schemas['PublicSearchJobsBody'];
128
130
 
129
131
  export type { CustomFieldValues as C, EmploymentType as E, JobSort as J, ListEnvelope as L, ObjectReferenceFilter as O, PublicJob as P, RemoteOption as R, Seniority as S, CustomFieldFilter as a, JobCollectionChoiceQuery as b, JobsListQuery as c, JobCardListEnvelope as d, JobsSearchBody as e, JobCardSearchEnvelope as f, JobsSimilarQuery as g, SearchEnvelope as h, CustomFieldValue as i, CustomFilterValue as j, EducationRequirement as k, JobCatalogPagination as l, JobCollectionChoiceList as m, JobCollectionFieldDefinition as n, JobCompany as o, OfficeLocation as p, OffsetPagination as q, PublicJobCard as r, RelatedSearch as s, RemotePermit as t, RemoteTimezone as u, ResolvedJobCollectionField as v, StorefrontPagination as w };
@@ -1,4 +1,4 @@
1
- import { S as Schemas } from './_spec-B_pBNVUd.mjs';
1
+ import { S as Schemas } from './_spec-DQvmsp5o.mjs';
2
2
 
3
3
  type Awaitable<T> = T | Promise<T>;
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { S as Schemas } from './_spec-B_pBNVUd.js';
1
+ import { S as Schemas } from './_spec-DQvmsp5o.js';
2
2
 
3
3
  type Awaitable<T> = T | Promise<T>;
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { S as Schemas } from './_spec-B_pBNVUd.js';
1
+ import { S as Schemas } from './_spec-DQvmsp5o.js';
2
2
 
3
3
  /**
4
4
  * The public SEO-infra payload (`board.seo()`) — platform infrastructure a
@@ -1,4 +1,4 @@
1
- import { S as Schemas } from './_spec-B_pBNVUd.mjs';
1
+ import { S as Schemas } from './_spec-DQvmsp5o.mjs';
2
2
 
3
3
  /**
4
4
  * The public SEO-infra payload (`board.seo()`) — platform infrastructure a
@@ -1,5 +1,5 @@
1
- import { B as BoardSeo } from './seo-0mbZ7M_E.mjs';
2
- import './_spec-B_pBNVUd.mjs';
1
+ import { B as BoardSeo } from './seo-DCQp8V1c.mjs';
2
+ import './_spec-DQvmsp5o.mjs';
3
3
 
4
4
  /**
5
5
  * `@cavuno/board/seo-files` — platform-owned crawler/identity file bodies
@@ -1,5 +1,5 @@
1
- import { B as BoardSeo } from './seo-C7gMl6Y7.js';
2
- import './_spec-B_pBNVUd.js';
1
+ import { B as BoardSeo } from './seo-CjNGqZZH.js';
2
+ import './_spec-DQvmsp5o.js';
3
3
 
4
4
  /**
5
5
  * `@cavuno/board/seo-files` — platform-owned crawler/identity file bodies
package/dist/seo.d.mts CHANGED
@@ -1,9 +1,9 @@
1
- import { P as PublicBlogPostSummary, B as BlogAuthorEmbed } from './blog-fhXRik6k.mjs';
2
- import { e as JsonLdObject } from './breadcrumbs-qE3CD--N.mjs';
3
- export { A as ALL_COUNTRY_CODES, B as BreadcrumbItemInput, g as FaqItem, J as JobBreadcrumb, h as JsonLdBoard, N as NumberNotation, i as SENIORITY_ORDER, j as SalaryFaqEntry, k as SalaryJsonLdOptions, l as SalaryOccupationListOptions, b as buildJobBreadcrumbs, m as buildSalaryFaq, o as companyCategorySalaryJsonLd, p as companySalaryJsonLd, q as createBreadcrumbJsonLd, r as createJobPostingJsonLd, s as crossAxisSalaryJsonLd, t as faqJsonLd, c as formatSalaryStat, d as formatSalaryStatRange, u as itemListJsonLd, v as locationSalaryJsonLd, n as normalizeWebsiteUrl, w as skillSalaryJsonLd, x as sortBySeniority, y as titleSalaryJsonLd } from './breadcrumbs-qE3CD--N.mjs';
4
- import './_spec-B_pBNVUd.mjs';
5
- import './board-DEiEHcpe.mjs';
6
- import './jobs-R-h3A3kl.mjs';
1
+ import { P as PublicBlogPostSummary, B as BlogAuthorEmbed } from './blog-CvuObn9O.mjs';
2
+ import { e as JsonLdObject } from './breadcrumbs-BBvJ5gq-.mjs';
3
+ export { A as ALL_COUNTRY_CODES, B as BreadcrumbItemInput, g as FaqItem, J as JobBreadcrumb, h as JsonLdBoard, N as NumberNotation, i as SENIORITY_ORDER, j as SalaryFaqEntry, k as SalaryJsonLdOptions, l as SalaryOccupationListOptions, b as buildJobBreadcrumbs, m as buildSalaryFaq, o as companyCategorySalaryJsonLd, p as companySalaryJsonLd, q as createBreadcrumbJsonLd, r as createJobPostingJsonLd, s as crossAxisSalaryJsonLd, t as faqJsonLd, c as formatSalaryStat, d as formatSalaryStatRange, u as itemListJsonLd, v as locationSalaryJsonLd, n as normalizeWebsiteUrl, w as skillSalaryJsonLd, x as sortBySeniority, y as titleSalaryJsonLd } from './breadcrumbs-BBvJ5gq-.mjs';
4
+ import './_spec-DQvmsp5o.mjs';
5
+ import './board-D6xOidUX.mjs';
6
+ import './jobs-DjMcghv6.mjs';
7
7
 
8
8
  /**
9
9
  * Blog structured data on the `@cavuno/board` wire types, transcribed from
package/dist/seo.d.ts CHANGED
@@ -1,9 +1,9 @@
1
- import { P as PublicBlogPostSummary, B as BlogAuthorEmbed } from './blog-DzGRUsj5.js';
2
- import { e as JsonLdObject } from './breadcrumbs-DNaaP3xa.js';
3
- export { A as ALL_COUNTRY_CODES, B as BreadcrumbItemInput, g as FaqItem, J as JobBreadcrumb, h as JsonLdBoard, N as NumberNotation, i as SENIORITY_ORDER, j as SalaryFaqEntry, k as SalaryJsonLdOptions, l as SalaryOccupationListOptions, b as buildJobBreadcrumbs, m as buildSalaryFaq, o as companyCategorySalaryJsonLd, p as companySalaryJsonLd, q as createBreadcrumbJsonLd, r as createJobPostingJsonLd, s as crossAxisSalaryJsonLd, t as faqJsonLd, c as formatSalaryStat, d as formatSalaryStatRange, u as itemListJsonLd, v as locationSalaryJsonLd, n as normalizeWebsiteUrl, w as skillSalaryJsonLd, x as sortBySeniority, y as titleSalaryJsonLd } from './breadcrumbs-DNaaP3xa.js';
4
- import './_spec-B_pBNVUd.js';
5
- import './board-fVAXZ8T5.js';
6
- import './jobs-BRysn2m0.js';
1
+ import { P as PublicBlogPostSummary, B as BlogAuthorEmbed } from './blog-CUkvhn3c.js';
2
+ import { e as JsonLdObject } from './breadcrumbs-D2Cwrju4.js';
3
+ export { A as ALL_COUNTRY_CODES, B as BreadcrumbItemInput, g as FaqItem, J as JobBreadcrumb, h as JsonLdBoard, N as NumberNotation, i as SENIORITY_ORDER, j as SalaryFaqEntry, k as SalaryJsonLdOptions, l as SalaryOccupationListOptions, b as buildJobBreadcrumbs, m as buildSalaryFaq, o as companyCategorySalaryJsonLd, p as companySalaryJsonLd, q as createBreadcrumbJsonLd, r as createJobPostingJsonLd, s as crossAxisSalaryJsonLd, t as faqJsonLd, c as formatSalaryStat, d as formatSalaryStatRange, u as itemListJsonLd, v as locationSalaryJsonLd, n as normalizeWebsiteUrl, w as skillSalaryJsonLd, x as sortBySeniority, y as titleSalaryJsonLd } from './breadcrumbs-D2Cwrju4.js';
4
+ import './_spec-DQvmsp5o.js';
5
+ import './board-CCG1pO6r.js';
6
+ import './jobs-CEIuahbz.js';
7
7
 
8
8
  /**
9
9
  * Blog structured data on the `@cavuno/board` wire types, transcribed from
package/dist/server.d.mts CHANGED
@@ -1,10 +1,10 @@
1
1
  import { BoardSdk } from './index.mjs';
2
- import './_spec-B_pBNVUd.mjs';
3
- import './search-BlB690ma.mjs';
4
- import './board-DEiEHcpe.mjs';
5
- import './jobs-R-h3A3kl.mjs';
6
- import './seo-0mbZ7M_E.mjs';
7
- import './blog-fhXRik6k.mjs';
2
+ import './_spec-DQvmsp5o.mjs';
3
+ import './search-C0LiPXVb.mjs';
4
+ import './board-D6xOidUX.mjs';
5
+ import './jobs-DjMcghv6.mjs';
6
+ import './seo-DCQp8V1c.mjs';
7
+ import './blog-CvuObn9O.mjs';
8
8
 
9
9
  /**
10
10
  * Session cookie codec — pure (no framework imports, no node imports) so it
package/dist/server.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import { BoardSdk } from './index.js';
2
- import './_spec-B_pBNVUd.js';
3
- import './search-BK_zHHNQ.js';
4
- import './board-fVAXZ8T5.js';
5
- import './jobs-BRysn2m0.js';
6
- import './seo-C7gMl6Y7.js';
7
- import './blog-DzGRUsj5.js';
2
+ import './_spec-DQvmsp5o.js';
3
+ import './search-D-EjtZH3.js';
4
+ import './board-CCG1pO6r.js';
5
+ import './jobs-CEIuahbz.js';
6
+ import './seo-CjNGqZZH.js';
7
+ import './blog-CUkvhn3c.js';
8
8
 
9
9
  /**
10
10
  * Session cookie codec — pure (no framework imports, no node imports) so it
package/dist/server.mjs CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-Z4ALWIGS.mjs";
5
5
  import {
6
6
  isUnauthorized
7
- } from "./chunk-DUTQOLOF.mjs";
7
+ } from "./chunk-EHSB2VAJ.mjs";
8
8
 
9
9
  // src/server/cookie.ts
10
10
  var COOKIE_ATTRIBUTES = "Path=/; HttpOnly; Secure; SameSite=Lax";
@@ -1,10 +1,10 @@
1
1
  import { BoardSdk } from './index.mjs';
2
- import './_spec-B_pBNVUd.mjs';
3
- import './search-BlB690ma.mjs';
4
- import './board-DEiEHcpe.mjs';
5
- import './jobs-R-h3A3kl.mjs';
6
- import './seo-0mbZ7M_E.mjs';
7
- import './blog-fhXRik6k.mjs';
2
+ import './_spec-DQvmsp5o.mjs';
3
+ import './search-C0LiPXVb.mjs';
4
+ import './board-D6xOidUX.mjs';
5
+ import './jobs-DjMcghv6.mjs';
6
+ import './seo-DCQp8V1c.mjs';
7
+ import './blog-CvuObn9O.mjs';
8
8
 
9
9
  /**
10
10
  * Sitemap primitives — the pure XML + bucket-filename logic behind a board
package/dist/sitemap.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import { BoardSdk } from './index.js';
2
- import './_spec-B_pBNVUd.js';
3
- import './search-BK_zHHNQ.js';
4
- import './board-fVAXZ8T5.js';
5
- import './jobs-BRysn2m0.js';
6
- import './seo-C7gMl6Y7.js';
7
- import './blog-DzGRUsj5.js';
2
+ import './_spec-DQvmsp5o.js';
3
+ import './search-D-EjtZH3.js';
4
+ import './board-CCG1pO6r.js';
5
+ import './jobs-CEIuahbz.js';
6
+ import './seo-CjNGqZZH.js';
7
+ import './blog-CUkvhn3c.js';
8
8
 
9
9
  /**
10
10
  * Sitemap primitives — the pure XML + bucket-filename logic behind a board
package/dist/sitemap.mjs CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-DLSO6XXY.mjs";
4
4
  import {
5
5
  isNotFound
6
- } from "./chunk-DUTQOLOF.mjs";
6
+ } from "./chunk-EHSB2VAJ.mjs";
7
7
  import {
8
8
  BOARD_PATHS,
9
9
  blogAuthorPath,
@@ -1,5 +1,5 @@
1
- import { h as SuggestionItem, b as SearchSuggestQuery, F as FetchOptions, g as SuggestResult } from './search-BlB690ma.mjs';
2
- import './_spec-B_pBNVUd.mjs';
1
+ import { h as SuggestionItem, b as SearchSuggestQuery, F as FetchOptions, g as SuggestResult } from './search-C0LiPXVb.mjs';
2
+ import './_spec-DQvmsp5o.mjs';
3
3
 
4
4
  /**
5
5
  * `@cavuno/board/suggest` — framework-agnostic headless search-suggest
package/dist/suggest.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { h as SuggestionItem, b as SearchSuggestQuery, F as FetchOptions, g as SuggestResult } from './search-BK_zHHNQ.js';
2
- import './_spec-B_pBNVUd.js';
1
+ import { h as SuggestionItem, b as SearchSuggestQuery, F as FetchOptions, g as SuggestResult } from './search-D-EjtZH3.js';
2
+ import './_spec-DQvmsp5o.js';
3
3
 
4
4
  /**
5
5
  * `@cavuno/board/suggest` — framework-agnostic headless search-suggest
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cavuno/board",
3
- "version": "4.27.0",
3
+ "version": "4.28.0",
4
4
  "description": "Build custom job boards and careers pages with Cavuno’s TypeScript SDK.",
5
5
  "keywords": [
6
6
  "api-client",
@@ -11,6 +11,31 @@ Anonymous methods here are token-based email unsubscribe and email-change confir
11
11
 
12
12
  The host application owns forms, file pickers, and cookie plumbing; this SDK surface supplies data operations.
13
13
 
14
+ ## Candidate screens are candidate-only
15
+
16
+ A board user is either a candidate or an employer (`me.role`). Only candidates have a candidate profile. Render candidate screens and fields (the profile form, profile completeness, resume upload, experience, education, skills, and languages) only when `me.role === 'candidate'`. On the account page, give an employer account the employer profile view instead: avatar, `displayName` (the one profile field every account can update), and a link to the employer screens (`board.me.companies.*`). Never show them the empty candidate form.
17
+
18
+ The API is the backstop: a profile write from an account with no candidate profile returns 403 `candidate_profile_required` before anything is saved. `displayName` alone is the one profile field every account can update. Surface this error as a message; never swallow it and report "Saved".
19
+
20
+ ```ts snippet
21
+ import { isBoardApiError } from '@cavuno/board';
22
+
23
+ const me = await board.me.retrieve();
24
+ if (me.role !== 'candidate') {
25
+ return renderEmployerProfile(me); // avatar, displayName, link to employer screens
26
+ }
27
+
28
+ try {
29
+ await board.me.profile.update({ headline: 'Staff Engineer' });
30
+ showSaved();
31
+ } catch (error) {
32
+ if (isBoardApiError(error) && error.code === 'candidate_profile_required') {
33
+ return showError('Only candidate accounts have a candidate profile.');
34
+ }
35
+ throw error;
36
+ }
37
+ ```
38
+
14
39
  ## Account and profile
15
40
 
16
41
  Account deletion is a synchronous, irreversible cascade over the profile, collections, saved jobs, alerts, avatar, and resume. Obtain explicit confirmation before calling it.
@@ -252,6 +277,8 @@ await board.me.notificationPreferences.unsubscribeWithToken({
252
277
 
253
278
  ## Completion gate
254
279
 
280
+ - Candidate screens render only for `me.role === 'candidate'`; an employer account gets the employer profile view (avatar, `displayName`, link to employer screens).
281
+ - A `candidate_profile_required` 403 shows an error and never a "Saved" state.
255
282
  - The profile form follows `forms.talent`: order matches, hidden fields are absent, and required fields block save while empty.
256
283
  - Profile update is visible after retrieval.
257
284
  - Scalar field updates preserve omitted values; collection selection updates preserve every selection the user meant to keep.
@@ -44,7 +44,7 @@ try {
44
44
  }
45
45
  ```
46
46
 
47
- The remaining status guards are `isForbidden` for 403 and `isConflict` for 409. Record `code` and `requestId` in support diagnostics. Because v1 may add error codes, unmatched `BoardApiError` instances still need a safe generic state.
47
+ The remaining status guards are `isForbidden` for 403 and `isConflict` for 409. A 403 `candidate_profile_required` means an account that is not a candidate (for example an employer) tried to write candidate profile data: gate candidate screens on `me.role === 'candidate'` and show the error rather than a success state. Record `code` and `requestId` in support diagnostics. Because v1 may add error codes, unmatched `BoardApiError` instances still need a safe generic state.
48
48
 
49
49
  **Complete when:** every expected failure has one typed branch, an invalid resource renders not-found, and unmatched failures retain their original error.
50
50
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: cavuno-board-filters
3
- description: Listing-filter contracts with @cavuno/board. Use for job filter controls, profile-field filters, collection choices, sort controls, listing URL validation, or taxonomy-backed filter options.
3
+ description: Listing-filter contracts with @cavuno/board. Use for job filter controls, job and company custom-field filters, profile-field filters, collection choices, sort controls, listing URL validation, or taxonomy-backed filter options.
4
4
  ---
5
5
 
6
6
  # Listing filters
@@ -150,6 +150,60 @@ const { items } = await board.search.suggest({
150
150
  The host router owns URL serialization and saved-filter persistence.
151
151
  Locations come from `board.taxonomy.places` rather than a static filter export.
152
152
 
153
+ ## Filter jobs by a job custom field
154
+
155
+ The operator's job custom fields arrive as definitions on
156
+ `board.context().customFields.job`. Expose a field as a filter only when the
157
+ operator intends it as one: keep an explicit list of filter keys in your app
158
+ config. Do not render a dropdown for every select field automatically; most
159
+ custom fields are details for the job page, not listing facets.
160
+
161
+ ```ts snippet
162
+ const context = await board.context();
163
+
164
+ // The operator picked these; everything else stays a job-page detail.
165
+ const FILTER_FIELD_KEYS = ['work_type'];
166
+ const filterFields = context.customFields.job.filter(
167
+ (field) =>
168
+ FILTER_FIELD_KEYS.includes(field.key) &&
169
+ (field.type === 'single_select' || field.type === 'multi_select'));
170
+
171
+ // Controls: option KEYS are the values, option labels are the text.
172
+ const controls = filterFields.map((field) => ({
173
+ key: field.key,
174
+ label: field.label,
175
+ options: (field.options ?? []).map((option) => ({
176
+ value: option.key,
177
+ label: option.label,
178
+ })),
179
+ }));
180
+
181
+ // URL state holds option keys (`/jobs?work_type=contract`), never labels.
182
+ // Drop anything that is not a current option before it reaches the API.
183
+ const customFields = filterFields.flatMap((field) => {
184
+ const values = searchParams
185
+ .getAll(field.key)
186
+ .filter((value) => field.options?.some((option) => option.key === value));
187
+ return values.length > 0 ? [{ key: field.key, values }] : [];
188
+ });
189
+
190
+ const results = await board.jobs.search({
191
+ query: filters.q,
192
+ filters: {
193
+ seniority: filters.seniority,
194
+ customFields: customFields.length > 0 ? customFields : undefined,
195
+ },
196
+ limit: 20,
197
+ });
198
+ ```
199
+
200
+ Clauses are AND-matched; values inside one clause are alternatives. Each
201
+ request takes at most 10 clauses of 10 values. An unknown key or an option key
202
+ the field no longer offers returns `invalid_filter`, so validate URL input
203
+ against the definitions as above. Boolean and number fields filter the same way
204
+ with `values: [true]` or `values: [3]`. Labels change when the operator renames
205
+ an option; keys do not, so bookmarked URLs keep working.
206
+
153
207
  ## Build filters from public profile fields
154
208
 
155
209
  `board.profileFields.retrieve('company')` and `retrieve('candidate')` return only public scalar and collection-reference definitions. Use each stored field key for filtering. For a collection reference, page or search its active public choices and submit the returned record `id`; the optional `logoUrl` is display metadata.
@@ -189,6 +243,36 @@ void talent.data;
189
243
 
190
244
  Clauses are AND-matched. Values or record IDs inside one clause are alternatives. Unknown, private, invalid, or archived choices return `invalid_filter`; each array accepts at most 10 clauses and each clause at most 10 choices. Job search supports scalar `customFields` under `filters`, but job collection references are outside this surface.
191
245
 
246
+ ## Filter jobs by job and company custom fields
247
+
248
+ `jobs.search` takes two custom-field filters. `filters.customFields` matches the job's own fields, defined in `board.context().customFields.job`. `filters.companyCustomFields` matches the public profile fields of the job's company, defined in `board.profileFields.retrieve('company')`. Both use the clause shape above; the two lists never share keys, so a job field and a company field may both be called `type`.
249
+
250
+ ```ts snippet
251
+ const [context, companyFields] = await Promise.all([
252
+ board.context(),
253
+ board.profileFields.retrieve('company'),
254
+ ]);
255
+ const opportunityType = context.customFields.job.find(
256
+ (field) => field.key === 'opportunity_type');
257
+ const employerType = companyFields.definitions.find(
258
+ (field) => field.key === 'employer_type');
259
+
260
+ const results = await board.jobs.search({
261
+ query: filters.q,
262
+ filters: {
263
+ customFields: [{ key: 'opportunity_type', values: ['identified'] }],
264
+ companyCustomFields: [{ key: 'employer_type', values: ['acco'] }],
265
+ },
266
+ limit: 20,
267
+ });
268
+
269
+ void opportunityType;
270
+ void employerType;
271
+ void results.data;
272
+ ```
273
+
274
+ Render each dropdown from the definition's `options` in order: show `label`, submit `key`, and keep the key in the URL so shared links survive a label change. Accept only keys the current definition offers; drop anything else from a hand-typed URL. Only `jobs.search` takes these filters. A company's jobs are re-indexed automatically when its values change. Worked example: [Custom field filters and badges](https://cavuno.com/docs/sdk/cookbook/custom-field-filters-and-badges).
275
+
192
276
  ## Completion gate
193
277
 
194
278
  Finish only after every applicable check passes:
@@ -204,8 +288,13 @@ Finish only after every applicable check passes:
204
288
  - `/jobs?category=engineering,design` sends
205
289
  `filters.categories: ['engineering', 'design']` to `jobs.search`.
206
290
  - Every paged taxonomy request forwards the previous opaque `nextCursor`.
291
+ - Job custom-field controls exist only for keys the operator chose as
292
+ filters, read their options from `board.context().customFields.job`, and send
293
+ option keys in `filters.customFields`; an unknown option in the URL never
294
+ reaches the API.
207
295
  - Company and talent custom controls come from `board.profileFields`; private definitions and archived choices never become filter options.
208
296
  - Profile-filter requests use stored scalar values and returned collection entry IDs, not display labels.
297
+ - Job-field dropdowns send `filters.customFields` and company-field dropdowns send `filters.companyCustomFields` to `jobs.search`, both built from the current definitions; a stale option key in the URL is dropped, not sent.
209
298
 
210
299
  ## Cavuno SDK reference
211
300