@cavuno/board 1.2.0 → 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/bin.mjs +74 -0
- package/dist/index.d.mts +207 -7
- package/dist/index.d.ts +207 -7
- package/dist/index.js +164 -7
- package/dist/index.mjs +164 -7
- package/dist/skills.d.mts +38 -0
- package/dist/skills.d.ts +38 -0
- package/dist/skills.js +62 -0
- package/dist/skills.mjs +29 -0
- package/package.json +22 -5
- package/skills/cavuno-board-auth/SKILL.md +113 -0
- package/skills/cavuno-board-client/SKILL.md +93 -0
- package/skills/cavuno-board-errors/SKILL.md +86 -0
- package/skills/cavuno-board-jobs/SKILL.md +93 -0
- package/skills/cavuno-board-setup/SKILL.md +96 -0
- package/skills/flavors/tanstack-start/SKILL.md +102 -0
- package/skills/manifest.json +47 -0
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;
|
|
@@ -352,9 +356,9 @@ interface PublicBoard {
|
|
|
352
356
|
*/
|
|
353
357
|
interface BoardSeo {
|
|
354
358
|
object: 'board_seo';
|
|
355
|
-
/** Verbatim `ads.txt` content, or `null` when not configured
|
|
359
|
+
/** Verbatim `ads.txt` content, or `null` when not configured. */
|
|
356
360
|
adsTxt: string | null;
|
|
357
|
-
/** IndexNow key-file content, or `null` when not configured
|
|
361
|
+
/** IndexNow key-file content, or `null` when not configured. */
|
|
358
362
|
indexNowKey: string | null;
|
|
359
363
|
/** Google site-verification token for the `<meta>` tag, or `null`. */
|
|
360
364
|
googleSiteVerification: string | 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.
|
|
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
|
-
|
|
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.
|
|
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)
|
|
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
|
-
/**
|
|
670
|
-
|
|
671
|
-
|
|
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
|
}
|