@cavuno/board 1.2.1 → 1.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.d.ts CHANGED
@@ -252,6 +252,8 @@ interface CustomStorage {
252
252
  type StorageMode = 'memory' | 'nostore' | CustomStorage;
253
253
  declare const ACCESS_TOKEN_KEY = "cavuno_board_access_token";
254
254
  declare const REFRESH_TOKEN_KEY = "cavuno_board_refresh_token";
255
+ /** Board-password access grant — sent as `X-Board-Access` to pass the wall. */
256
+ declare const BOARD_ACCESS_GRANT_KEY = "cavuno_board_access_grant";
255
257
 
256
258
  /**
257
259
  * The request handed to `onRequest` and `fetch`. `onRequest` may
@@ -340,6 +342,8 @@ interface PublicBoard {
340
342
  language: string;
341
343
  logoUrl: string | null;
342
344
  primaryDomain: string | null;
345
+ /** Whitelabel toggle (default `true`) — render the "Powered by Cavuno" badge unless `false`. */
346
+ showCavunoBranding: boolean;
343
347
  features: PublicBoardFeatures;
344
348
  analytics: PublicBoardAnalytics;
345
349
  theme: PublicBoardTheme | null;
@@ -402,6 +406,12 @@ declare class BoardApiError extends Error {
402
406
  declare function isBoardApiError(e: unknown): e is BoardApiError;
403
407
  declare function isNotFound(e: unknown): e is BoardApiError;
404
408
  declare function isUnauthorized(e: unknown): e is BoardApiError;
409
+ /**
410
+ * The board is password-protected and the read carried no valid `X-Board-Access`
411
+ * grant. Distinct from an expired board-USER token (also 401): this code means
412
+ * "call `password.verify()` again to mint a fresh grant", not "re-login".
413
+ */
414
+ declare function isBoardPasswordRequired(e: unknown): e is BoardApiError;
405
415
  declare function isForbidden(e: unknown): e is BoardApiError;
406
416
  declare function isValidationError(e: unknown): e is BoardApiError;
407
417
  declare function isRateLimited(e: unknown): e is BoardApiError;
@@ -413,7 +423,7 @@ declare function isConflict(e: unknown): e is BoardApiError;
413
423
  * constant because the package is platform-neutral and cannot read
414
424
  * package.json at runtime.
415
425
  */
416
- declare const SDK_VERSION = "1.2.1";
426
+ declare const SDK_VERSION = "1.3.0";
417
427
 
418
428
  interface BoardUser {
419
429
  id: string;
@@ -464,6 +474,41 @@ interface ResetPasswordBody {
464
474
  password: string;
465
475
  }
466
476
 
477
+ /**
478
+ * Query for `board.embed.jobs()` — the embeddable, UNGATED jobs widget. Mirrors
479
+ * the browse list's facets + geo, plus a free-text `q` keyword (the widget is
480
+ * searchable). Deliberately has NO `category`/`skill` programmatic seeding and
481
+ * NO `fields` sparse fieldset — the embed widget exposes none of those.
482
+ */
483
+ type EmbedJobsQuery = {
484
+ /** Free-text search query, up to 200 characters. */
485
+ q?: string;
486
+ cursor?: string;
487
+ /** Default 8; values above the embed ceiling of 50 are clamped to 50. */
488
+ limit?: number;
489
+ /** Storefront page offset; takes precedence over `cursor`. `offset + limit` ≤ 10,000. */
490
+ offset?: number;
491
+ /** Single or repeated (up to 10) — repeated params are OR-matched. */
492
+ companyId?: string | string[];
493
+ remoteOption?: RemoteOption | RemoteOption[];
494
+ employmentType?: EmploymentType | EmploymentType[];
495
+ seniority?: Seniority | Seniority[];
496
+ /** Place slug for a geo radius search; unresolvable slugs are ignored. */
497
+ location?: string;
498
+ /** Radius in km around `location` (10–250; default 50). */
499
+ radius?: number;
500
+ };
501
+
502
+ /**
503
+ * The board-access grant returned by `password.verify()`. Send `token` as the
504
+ * `X-Board-Access` header on content reads to pass a board's password wall
505
+ * (the SDK does this automatically once the grant is stored).
506
+ */
507
+ interface BoardAccessGrant {
508
+ object: 'board_access_grant';
509
+ token: string;
510
+ }
511
+
467
512
  /**
468
513
  * The result of resolving a path against the board's configured redirects
469
514
  * (`board.redirects.resolve()`). A headless frontend 308s to `target`, or 404s
@@ -509,9 +554,13 @@ interface PublicBlogPostSummary {
509
554
  slug: string;
510
555
  featured: boolean;
511
556
  coverUrl: string | null;
557
+ /** Alt text for `coverUrl` (the cover/feature image). */
558
+ featureImageAlt: string | null;
512
559
  customExcerpt: string | null;
513
560
  readingTimeMin: number | null;
514
561
  publishedAt: string | null;
562
+ /** A custom canonical URL override for this post, if set. */
563
+ canonicalUrl: string | null;
515
564
  createdAt: string;
516
565
  authors: BlogAuthorEmbed[];
517
566
  tags: BlogTagEmbed[];
@@ -520,11 +569,22 @@ interface PublicBlogPostSummary {
520
569
  interface PublicBlogPost extends PublicBlogPostSummary {
521
570
  html: string | null;
522
571
  ogImageUrl: string | null;
523
- featureImageAlt: string | null;
524
572
  featureImageCaption: string | null;
525
573
  seoTitle: string | null;
526
574
  seoDescription: string | null;
527
- canonicalUrl: string | null;
575
+ /**
576
+ * Slug-history resolution. When the requested slug is a renamed post's OLD
577
+ * slug, `redirected` is `true` and `newSlug` is the post's current slug —
578
+ * issue a 308 to it. Otherwise `redirected` is `false` and `newSlug` is null.
579
+ */
580
+ redirected: boolean;
581
+ newSlug: string | null;
582
+ }
583
+ /** Previous (older) + next (newer) posts for the detail prev/next nav. */
584
+ interface PublicBlogAdjacentPosts {
585
+ object: 'blog_adjacent_posts';
586
+ previous: PublicBlogPostSummary | null;
587
+ next: PublicBlogPostSummary | null;
528
588
  }
529
589
  type BlogPostsListQuery = {
530
590
  cursor?: string;
@@ -535,6 +595,10 @@ type BlogPostsListQuery = {
535
595
  /** Opt-in only: pass `'true'` to restrict to featured posts. */
536
596
  featured?: 'true';
537
597
  };
598
+ type BlogSimilarQuery = {
599
+ /** 1–20; default 6. */
600
+ limit?: number;
601
+ };
538
602
  interface BlogSearchBody {
539
603
  /** Free-text query, 1–200 characters. Required. */
540
604
  query: string;
@@ -596,6 +660,109 @@ interface SaveJobBody {
596
660
  jobId: string;
597
661
  }
598
662
 
663
+ type JobAlertFrequency = 'daily' | 'weekly';
664
+ type JobAlertRemoteOption = 'on_site' | 'hybrid' | 'remote';
665
+ /**
666
+ * Alert filters a consumer can capture. Only `jobFunctions` (→ job categories),
667
+ * `placeSlugs` (→ place IDs) and `remoteOptions` scope the digest server-side;
668
+ * `seniorityLevels`/`salary*` are stored but do not filter delivery.
669
+ */
670
+ type JobAlertFiltersInput = {
671
+ jobFunctions?: string[];
672
+ seniorityLevels?: string[];
673
+ remoteOptions?: JobAlertRemoteOption[];
674
+ placeSlugs?: string[];
675
+ salaryMin?: number;
676
+ salaryMax?: number;
677
+ salaryCurrency?: string;
678
+ };
679
+ /** Body for `jobAlerts.subscribe`. `consent` must be `true` (server-enforced). */
680
+ type JobAlertSubscribeInput = {
681
+ email: string;
682
+ consent: true;
683
+ frequency?: JobAlertFrequency;
684
+ filters?: JobAlertFiltersInput;
685
+ /** Scopes the alert to the originating job/search (for the digest + analytics). */
686
+ context?: {
687
+ source?: string;
688
+ jobId?: string;
689
+ jobSlug?: string;
690
+ };
691
+ };
692
+ interface JobAlertSubscription {
693
+ object: 'job_alert_subscription';
694
+ status: 'created' | 'duplicate';
695
+ requiresConfirmation: boolean;
696
+ confirmed: boolean;
697
+ }
698
+ interface JobAlertConfirmation {
699
+ object: 'job_alert_confirmation';
700
+ status: 'confirmed' | 'already_confirmed' | 'expired' | 'not_found';
701
+ }
702
+ interface JobAlertResendResult {
703
+ object: 'job_alert_confirmation_resend';
704
+ status: 'sent' | 'not_found' | 'already_confirmed' | 'throttled' | 'send_failed';
705
+ }
706
+ interface JobAlertManageResult {
707
+ object: 'job_alert_manage_result';
708
+ success: boolean;
709
+ }
710
+ /**
711
+ * The stored filter criteria surfaced on the manage page (a serializable subset
712
+ * of the internal filter object — the fields a consumer renders). `jobFunctions`,
713
+ * `placeIds` and `remoteOptions` are the dimensions the digest actually matches on.
714
+ */
715
+ interface JobAlertStoredFilters {
716
+ jobFunctions?: string[];
717
+ seniorityLevels?: string[];
718
+ remoteOptions?: string[];
719
+ placeIds?: string[];
720
+ salaryMin?: number | null;
721
+ salaryMax?: number | null;
722
+ salaryCurrency?: string | null;
723
+ frequency?: string;
724
+ }
725
+ interface JobAlertPreference {
726
+ id: string;
727
+ label: string | null;
728
+ frequency: string;
729
+ isActive: boolean;
730
+ filters: JobAlertStoredFilters;
731
+ /** Per-preference HMAC token for `updatePreference`/`deletePreference`. */
732
+ manageToken: string;
733
+ }
734
+ interface JobAlertManageState {
735
+ object: 'job_alert_manage_state';
736
+ email: string;
737
+ confirmed: boolean;
738
+ unsubscribed: boolean;
739
+ preferences: JobAlertPreference[];
740
+ }
741
+ /** Query for `jobAlerts.manage` — the HMAC manage token from the digest email. */
742
+ type JobAlertManageQuery = {
743
+ subscription: string;
744
+ token: string;
745
+ };
746
+ /** Body for `jobAlerts.unsubscribe` / `resubscribe` (HMAC manage token). */
747
+ type JobAlertManageTokenInput = {
748
+ subscriptionId: string;
749
+ token: string;
750
+ preferenceId?: string;
751
+ };
752
+ type JobAlertUpdatePreferenceInput = {
753
+ subscriptionId: string;
754
+ preferenceId: string;
755
+ token: string;
756
+ /** Required — the update is a full replace; restate it to avoid resetting cadence. */
757
+ frequency: JobAlertFrequency;
758
+ filters?: JobAlertFiltersInput;
759
+ };
760
+ type JobAlertDeletePreferenceInput = {
761
+ subscriptionId: string;
762
+ preferenceId: string;
763
+ token: string;
764
+ };
765
+
599
766
  interface TaxonomyGeo {
600
767
  lat: number | null;
601
768
  lng: number | null;
@@ -622,6 +789,17 @@ interface TaxonomyResolution {
622
789
  redirectTo: string | null;
623
790
  geo: TaxonomyGeo | null;
624
791
  }
792
+ /**
793
+ * Query for `taxonomy.places.list()`. Omit it (or `q`) for the full locations
794
+ * directory; pass `q` (≥2 chars) for location autocomplete — the top name
795
+ * matches ranked, what a location search field renders.
796
+ */
797
+ type PlacesListQuery = {
798
+ /** Location autocomplete query; ≥2 chars → top name matches, under 2 → empty. */
799
+ q?: string;
800
+ /** Max autocomplete results when `q` is given (1–50; default 10). */
801
+ limit?: number;
802
+ };
625
803
  /**
626
804
  * A place in the board's locations directory (`taxonomy.places.list()` →
627
805
  * `GET /places`), the data the `/jobs/locations/` index renders. `jobCount` is
@@ -701,6 +879,9 @@ declare function createBoardClient(options: CreateBoardClientOptions): {
701
879
  search(body: JobsSearchBody, query?: Record<string, never>, options?: FetchOptions): Promise<JobCardSearchEnvelope>;
702
880
  similar(jobSlug: string, query?: JobsSimilarQuery, options?: FetchOptions): Promise<ListEnvelope<PublicJobCard>>;
703
881
  };
882
+ embed: {
883
+ jobs(query?: EmbedJobsQuery, options?: FetchOptions): Promise<ListEnvelope<PublicJobCard>>;
884
+ };
704
885
  companies: {
705
886
  list(query?: CompaniesListQuery, options?: FetchOptions): Promise<ListEnvelope<PublicCompany>>;
706
887
  retrieve(companySlug: string, query?: Record<string, never>, options?: FetchOptions): Promise<PublicCompany>;
@@ -711,6 +892,8 @@ declare function createBoardClient(options: CreateBoardClientOptions): {
711
892
  posts: {
712
893
  list(query?: BlogPostsListQuery, options?: FetchOptions): Promise<ListEnvelope<PublicBlogPostSummary>>;
713
894
  retrieve(postSlug: string, query?: Record<string, never>, options?: FetchOptions): Promise<PublicBlogPost>;
895
+ adjacent(postSlug: string, options?: FetchOptions): Promise<PublicBlogAdjacentPosts>;
896
+ similar(postSlug: string, query?: BlogSimilarQuery, options?: FetchOptions): Promise<ListEnvelope<PublicBlogPostSummary>>;
714
897
  };
715
898
  tags: {
716
899
  list(query?: Record<string, never>, options?: FetchOptions): Promise<ListEnvelope<PublicBlogTag>>;
@@ -739,6 +922,9 @@ declare function createBoardClient(options: CreateBoardClientOptions): {
739
922
  unsave(jobId: string, query?: Record<string, never>, options?: FetchOptions): Promise<void>;
740
923
  };
741
924
  };
925
+ password: {
926
+ verify(password: string, options?: FetchOptions): Promise<BoardAccessGrant>;
927
+ };
742
928
  taxonomy: {
743
929
  categories: {
744
930
  resolve(slug: string, options?: FetchOptions): Promise<TaxonomyResolution>;
@@ -747,14 +933,28 @@ declare function createBoardClient(options: CreateBoardClientOptions): {
747
933
  resolve(slug: string, options?: FetchOptions): Promise<TaxonomyResolution>;
748
934
  };
749
935
  places: {
750
- list(options?: FetchOptions): Promise<ListEnvelope<PublicPlace>>;
936
+ list(query?: PlacesListQuery, options?: FetchOptions): Promise<ListEnvelope<PublicPlace>>;
751
937
  resolve(slug: string, options?: FetchOptions): Promise<TaxonomyResolution>;
752
938
  };
753
939
  };
754
940
  redirects: {
755
941
  resolve(path: string, options?: FetchOptions): Promise<RedirectResolution>;
756
942
  };
943
+ jobAlerts: {
944
+ subscribe(input: JobAlertSubscribeInput, options?: FetchOptions): Promise<JobAlertSubscription>;
945
+ confirm(input: {
946
+ token: string;
947
+ }, options?: FetchOptions): Promise<JobAlertConfirmation>;
948
+ resendConfirmation(input: {
949
+ email: string;
950
+ }, options?: FetchOptions): Promise<JobAlertResendResult>;
951
+ manage(query: JobAlertManageQuery, options?: FetchOptions): Promise<JobAlertManageState>;
952
+ unsubscribe(input: JobAlertManageTokenInput, options?: FetchOptions): Promise<JobAlertManageResult>;
953
+ resubscribe(input: JobAlertManageTokenInput, options?: FetchOptions): Promise<JobAlertManageResult>;
954
+ updatePreference(input: JobAlertUpdatePreferenceInput, options?: FetchOptions): Promise<JobAlertManageResult>;
955
+ deletePreference(input: JobAlertDeletePreferenceInput, options?: FetchOptions): Promise<JobAlertManageResult>;
956
+ };
757
957
  };
758
958
  type BoardSdk = ReturnType<typeof createBoardClient>;
759
959
 
760
- export { ACCESS_TOKEN_KEY, type Awaitable, type BlogAuthorEmbed, type BlogPostsListQuery, type BlogSearchBody, type BlogTagEmbed, BoardApiError, type BoardAuthSession, BoardClient, type BoardRequest, type BoardSdk, type BoardSeo, type BoardUser, type CompaniesListQuery, type CompaniesSearchBody, type CompanyJobsListQuery, type CreateBoardClientOptions, type CustomStorage, type EducationRequirement, type EmploymentType, type FetchOptions, type ForgotPasswordBody, type JobCardListEnvelope, type JobCardSearchEnvelope, type JobCompany, type JobsListQuery, type JobsSearchBody, type ListEnvelope, type Logger, type LoginBody, type LogoutBody, type OfficeLocation, type PublicBlogAuthor, type PublicBlogPost, type PublicBlogPostSummary, type PublicBlogTag, type PublicBoard, type PublicBoardAnalytics, type PublicBoardFeatures, type PublicBoardTheme, type PublicCompany, type PublicJob, type PublicJobCard, type PublicPlace, REFRESH_TOKEN_KEY, type RedirectResolution, type RefreshBody, type RegisterBody, type RelatedSearch, type RemoteOption, type RemotePermit, type RemoteTimezone, type ResetPasswordBody, SDK_VERSION, type SaveJobBody, type SavedJob, type SavedJobsListQuery, type SearchEnvelope, type Seniority, type StorageMode, type StorefrontPagination, type TaxonomyGeo, type TaxonomyResolution, type VerifyEmailBody, createBoardClient, isBoardApiError, isConflict, isForbidden, isNotFound, isRateLimited, isUnauthorized, isValidationError };
960
+ export { ACCESS_TOKEN_KEY, type Awaitable, BOARD_ACCESS_GRANT_KEY, type BlogAuthorEmbed, type BlogPostsListQuery, type BlogSearchBody, type BlogSimilarQuery, type BlogTagEmbed, type BoardAccessGrant, BoardApiError, type BoardAuthSession, BoardClient, type BoardRequest, type BoardSdk, type BoardSeo, type BoardUser, type CompaniesListQuery, type CompaniesSearchBody, type CompanyJobsListQuery, type CreateBoardClientOptions, type CustomStorage, type EducationRequirement, type EmbedJobsQuery, type EmploymentType, type FetchOptions, type ForgotPasswordBody, type JobAlertConfirmation, type JobAlertDeletePreferenceInput, type JobAlertFiltersInput, type JobAlertFrequency, type JobAlertManageQuery, type JobAlertManageResult, type JobAlertManageState, type JobAlertManageTokenInput, type JobAlertPreference, type JobAlertRemoteOption, type JobAlertResendResult, type JobAlertStoredFilters, type JobAlertSubscribeInput, type JobAlertSubscription, type JobAlertUpdatePreferenceInput, type JobCardListEnvelope, type JobCardSearchEnvelope, type JobCompany, type JobsListQuery, type JobsSearchBody, type ListEnvelope, type Logger, type LoginBody, type LogoutBody, type OfficeLocation, type PlacesListQuery, type PublicBlogAdjacentPosts, type PublicBlogAuthor, type PublicBlogPost, type PublicBlogPostSummary, type PublicBlogTag, type PublicBoard, type PublicBoardAnalytics, type PublicBoardFeatures, type PublicBoardTheme, type PublicCompany, type PublicJob, type PublicJobCard, type PublicPlace, REFRESH_TOKEN_KEY, type RedirectResolution, type RefreshBody, type RegisterBody, type RelatedSearch, type RemoteOption, type RemotePermit, type RemoteTimezone, type ResetPasswordBody, SDK_VERSION, type SaveJobBody, type SavedJob, type SavedJobsListQuery, type SearchEnvelope, type Seniority, type StorageMode, type StorefrontPagination, type TaxonomyGeo, type TaxonomyResolution, type VerifyEmailBody, createBoardClient, isBoardApiError, isBoardPasswordRequired, isConflict, isForbidden, isNotFound, isRateLimited, isUnauthorized, isValidationError };
package/dist/index.js CHANGED
@@ -21,12 +21,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  ACCESS_TOKEN_KEY: () => ACCESS_TOKEN_KEY,
24
+ BOARD_ACCESS_GRANT_KEY: () => BOARD_ACCESS_GRANT_KEY,
24
25
  BoardApiError: () => BoardApiError,
25
26
  BoardClient: () => BoardClient,
26
27
  REFRESH_TOKEN_KEY: () => REFRESH_TOKEN_KEY,
27
28
  SDK_VERSION: () => SDK_VERSION,
28
29
  createBoardClient: () => createBoardClient,
29
30
  isBoardApiError: () => isBoardApiError,
31
+ isBoardPasswordRequired: () => isBoardPasswordRequired,
30
32
  isConflict: () => isConflict,
31
33
  isForbidden: () => isForbidden,
32
34
  isNotFound: () => isNotFound,
@@ -65,6 +67,9 @@ function isNotFound(e) {
65
67
  function isUnauthorized(e) {
66
68
  return isBoardApiError(e) && e.status === 401;
67
69
  }
70
+ function isBoardPasswordRequired(e) {
71
+ return isBoardApiError(e) && e.status === 401 && e.code === "board_password_required";
72
+ }
68
73
  function isForbidden(e) {
69
74
  return isBoardApiError(e) && e.status === 403;
70
75
  }
@@ -99,6 +104,7 @@ function toSearchParams(query) {
99
104
  // src/storage.ts
100
105
  var ACCESS_TOKEN_KEY = "cavuno_board_access_token";
101
106
  var REFRESH_TOKEN_KEY = "cavuno_board_refresh_token";
107
+ var BOARD_ACCESS_GRANT_KEY = "cavuno_board_access_grant";
102
108
  function isBrowser() {
103
109
  return typeof globalThis.document !== "undefined";
104
110
  }
@@ -143,7 +149,7 @@ async function clearSession(storage) {
143
149
  }
144
150
 
145
151
  // src/version.ts
146
- var SDK_VERSION = "1.2.1";
152
+ var SDK_VERSION = "1.3.0";
147
153
 
148
154
  // src/client.ts
149
155
  function isRawBody(body) {
@@ -185,12 +191,14 @@ var BoardClient = class {
185
191
  }
186
192
  const token = await this.storage.getItem(ACCESS_TOKEN_KEY);
187
193
  if (token !== null) headers.set("authorization", `Bearer ${token}`);
194
+ const grant = await this.storage.getItem(BOARD_ACCESS_GRANT_KEY);
195
+ if (grant !== null) headers.set("x-board-access", grant);
188
196
  if (callHeaders) {
189
197
  new Headers(callHeaders).forEach((value, key) => {
190
198
  headers.set(key, value);
191
199
  });
192
200
  }
193
- if ((method !== "GET" || headers.has("authorization")) && !headers.has("x-cavuno-sdk")) {
201
+ if ((method !== "GET" || headers.has("authorization") || headers.has("x-board-access")) && !headers.has("x-cavuno-sdk")) {
194
202
  headers.set("x-cavuno-sdk", `board@${SDK_VERSION}`);
195
203
  }
196
204
  let req = {
@@ -201,7 +209,8 @@ var BoardClient = class {
201
209
  this.options.logger?.debug(`${method} ${req.url}`);
202
210
  const res = await globalThis.fetch(req.url, req.init);
203
211
  this.options.logger?.debug(`${res.status} ${method} ${req.url}`);
204
- if (this.options.onResponse) await this.options.onResponse(res.clone(), req);
212
+ if (this.options.onResponse)
213
+ await this.options.onResponse(res.clone(), req);
205
214
  if (res.status === 204) return void 0;
206
215
  if (res.ok) return await res.json();
207
216
  let parsed;
@@ -396,6 +405,30 @@ function blogNamespace(client) {
396
405
  `/blog/posts/${encodeURIComponent(postSlug)}`,
397
406
  { ...options, query }
398
407
  );
408
+ },
409
+ /**
410
+ * The previous (older) and next (newer) posts for prev/next navigation.
411
+ *
412
+ * @example
413
+ * const { previous, next } = await board.blog.posts.adjacent('hello-world');
414
+ */
415
+ adjacent(postSlug, options) {
416
+ return client.fetch(
417
+ `/blog/posts/${encodeURIComponent(postSlug)}/adjacent`,
418
+ options
419
+ );
420
+ },
421
+ /**
422
+ * Posts most similar to one post (the related-posts rail).
423
+ *
424
+ * @example
425
+ * const { data } = await board.blog.posts.similar('hello-world', { limit: 6 });
426
+ */
427
+ similar(postSlug, query, options) {
428
+ return client.fetch(
429
+ `/blog/posts/${encodeURIComponent(postSlug)}/similar`,
430
+ { ...options, query }
431
+ );
399
432
  }
400
433
  },
401
434
  tags: {
@@ -501,6 +534,96 @@ function companiesNamespace(client) {
501
534
  };
502
535
  }
503
536
 
537
+ // src/namespaces/embed.ts
538
+ function embedNamespace(client) {
539
+ return {
540
+ /**
541
+ * List published jobs for an embeddable widget — the same featured-ranked
542
+ * cards as `board.jobs.list`, but UNGATED: the candidate paywall never
543
+ * applies, so the full page is always returned and there is no
544
+ * `gatedCount`. Powers the public "Powered by Cavuno" embed. `limit`
545
+ * defaults to 8 and is clamped to a maximum of 50.
546
+ *
547
+ * @example
548
+ * const { data, nextCursor } = await board.embed.jobs({ q: 'chef', limit: 8 });
549
+ */
550
+ jobs(query, options) {
551
+ return client.fetch("/embed/jobs", {
552
+ ...options,
553
+ query
554
+ });
555
+ }
556
+ };
557
+ }
558
+
559
+ // src/namespaces/job-alerts.ts
560
+ function jobAlertsNamespace(client) {
561
+ return {
562
+ /** Subscribe an email to job alerts. Sends a double-opt-in confirmation email. */
563
+ subscribe(input, options) {
564
+ return client.fetch("/job-alerts", {
565
+ ...options,
566
+ method: "POST",
567
+ body: input
568
+ });
569
+ },
570
+ /** Complete double opt-in with the token from the confirmation email. */
571
+ confirm(input, options) {
572
+ return client.fetch("/job-alerts/confirm", {
573
+ ...options,
574
+ method: "POST",
575
+ body: input
576
+ });
577
+ },
578
+ /** Re-send the confirmation email for an unconfirmed subscription. */
579
+ resendConfirmation(input, options) {
580
+ return client.fetch(
581
+ "/job-alerts/resend-confirmation",
582
+ { ...options, method: "POST", body: input }
583
+ );
584
+ },
585
+ /** Read a subscription + its preferences for the manage page (HMAC token). */
586
+ manage(query, options) {
587
+ return client.fetch("/job-alerts/manage", {
588
+ ...options,
589
+ query
590
+ });
591
+ },
592
+ /** Deactivate a subscription via the HMAC manage token. */
593
+ unsubscribe(input, options) {
594
+ return client.fetch("/job-alerts/unsubscribe", {
595
+ ...options,
596
+ method: "POST",
597
+ body: input
598
+ });
599
+ },
600
+ /** Re-activate a previously unsubscribed subscription via the manage token. */
601
+ resubscribe(input, options) {
602
+ return client.fetch("/job-alerts/resubscribe", {
603
+ ...options,
604
+ method: "POST",
605
+ body: input
606
+ });
607
+ },
608
+ /** Edit a preference's filters/frequency via the manage token. */
609
+ updatePreference(input, options) {
610
+ return client.fetch("/job-alerts/preferences", {
611
+ ...options,
612
+ method: "POST",
613
+ body: input
614
+ });
615
+ },
616
+ /** Delete a preference via the manage token. */
617
+ deletePreference(input, options) {
618
+ return client.fetch("/job-alerts/preferences", {
619
+ ...options,
620
+ method: "DELETE",
621
+ body: input
622
+ });
623
+ }
624
+ };
625
+ }
626
+
504
627
  // src/namespaces/jobs.ts
505
628
  function jobsNamespace(client) {
506
629
  return {
@@ -625,6 +748,30 @@ function meNamespace(client) {
625
748
  };
626
749
  }
627
750
 
751
+ // src/namespaces/password.ts
752
+ function passwordNamespace(client) {
753
+ return {
754
+ /**
755
+ * Exchange a board password for an access grant and store it. After this
756
+ * resolves, every subsequent read auto-carries the grant as the
757
+ * `X-Board-Access` header — until the password rotates, after which reads
758
+ * fail with 401 `board_password_required` and you must `verify()` again
759
+ * (the SDK never auto-retries — verify is rate-limited — and never
760
+ * auto-clears; the host re-challenges). On the server (`nostore` storage)
761
+ * the grant is returned but not persisted; pass it per-call instead.
762
+ */
763
+ async verify(password, options) {
764
+ const grant = await client.fetch("/password/verify", {
765
+ ...options,
766
+ method: "POST",
767
+ body: { password }
768
+ });
769
+ await client.storage.setItem(BOARD_ACCESS_GRANT_KEY, grant.token);
770
+ return grant;
771
+ }
772
+ };
773
+ }
774
+
628
775
  // src/namespaces/redirects.ts
629
776
  function redirectsNamespace(client) {
630
777
  return {
@@ -666,9 +813,16 @@ function taxonomyNamespace(client) {
666
813
  skills: taxonomyResolver(client, "skills"),
667
814
  places: {
668
815
  ...taxonomyResolver(client, "places"),
669
- /** List every place used by a published job, with its live job count. */
670
- list(options) {
671
- return client.fetch("/places", options);
816
+ /**
817
+ * Without `query`: list every place used by a published job, with its
818
+ * live job count (the locations directory). With `query.q` (≥2 chars):
819
+ * location autocomplete — the top name matches ranked.
820
+ */
821
+ list(query, options) {
822
+ return client.fetch("/places", {
823
+ ...options,
824
+ query
825
+ });
672
826
  }
673
827
  }
674
828
  };
@@ -713,11 +867,14 @@ function createBoardClient(options) {
713
867
  return client.fetch("/seo", options2);
714
868
  },
715
869
  jobs: jobsNamespace(client),
870
+ embed: embedNamespace(client),
716
871
  companies: companiesNamespace(client),
717
872
  blog: blogNamespace(client),
718
873
  auth: authNamespace(client),
719
874
  me: meNamespace(client),
875
+ password: passwordNamespace(client),
720
876
  taxonomy: taxonomyNamespace(client),
721
- redirects: redirectsNamespace(client)
877
+ redirects: redirectsNamespace(client),
878
+ jobAlerts: jobAlertsNamespace(client)
722
879
  };
723
880
  }