@cavuno/board 1.0.0 → 1.1.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.mts CHANGED
@@ -1,3 +1,244 @@
1
+ /**
2
+ * Stripe-shaped success envelopes (`01-conventions.md` §5.1). The
3
+ * server MAY add top-level fields; consumers MUST ignore unknown
4
+ * fields.
5
+ */
6
+ /**
7
+ * Medusa-style storefront pagination fields (ADR-0037 §7), populated only by
8
+ * the board jobs catalog reads (browse / search / company-jobs); absent on
9
+ * every other list/search. `nextCursor` is preserved alongside (offset-encoded).
10
+ */
11
+ interface StorefrontPagination {
12
+ /** Total matching results ("X jobs"). */
13
+ count?: number;
14
+ /** The page size used for this response. */
15
+ limit?: number;
16
+ /** Number of items skipped before this page. */
17
+ offset?: number;
18
+ /** Items hidden behind the candidate paywall for the current viewer; absent/0 when entitled. */
19
+ gatedCount?: number;
20
+ }
21
+ interface ListEnvelope<T> extends StorefrontPagination {
22
+ object: 'list';
23
+ url: string;
24
+ hasMore: boolean;
25
+ /** `null` when `hasMore` is false — always present, never undefined. */
26
+ nextCursor: string | null;
27
+ data: T[];
28
+ }
29
+ interface SearchEnvelope<T> extends StorefrontPagination {
30
+ object: 'search_result';
31
+ url: string;
32
+ hasMore: boolean;
33
+ nextCursor: string | null;
34
+ data: T[];
35
+ }
36
+
37
+ type RemoteOption = 'on_site' | 'hybrid' | 'remote';
38
+ type EmploymentType = 'full_time' | 'part_time' | 'contract' | 'internship' | 'temporary' | 'volunteer' | 'other';
39
+ type Seniority = 'entry_level' | 'associate' | 'mid_level' | 'senior' | 'lead' | 'principal' | 'director' | 'executive';
40
+ type EducationRequirement = 'high_school' | 'associate_degree' | 'bachelor_degree' | 'professional_certificate' | 'postgraduate_degree' | 'no_requirements';
41
+ interface OfficeLocation {
42
+ countryCode: string | null;
43
+ country: string | null;
44
+ locality: string | null;
45
+ city: string | null;
46
+ region: string | null;
47
+ regionCode: string | null;
48
+ postalCode: string | null;
49
+ displayName: string | null;
50
+ }
51
+ interface JobCompany {
52
+ id: string;
53
+ name: string | null;
54
+ slug: string | null;
55
+ logoUrl: string | null;
56
+ website: string | null;
57
+ }
58
+ interface RemotePermit {
59
+ type: string;
60
+ value: string;
61
+ }
62
+ interface RemoteTimezone {
63
+ type: string;
64
+ value: string;
65
+ plusMinus?: number;
66
+ }
67
+ interface PublicJob {
68
+ id: string;
69
+ object: 'public_job';
70
+ title: string;
71
+ slug: string | null;
72
+ status: string;
73
+ companyId: string | null;
74
+ employmentType: string | null;
75
+ remoteOption: string | null;
76
+ seniority: string | null;
77
+ salaryMin: number | null;
78
+ salaryMax: number | null;
79
+ salaryCurrency: string | null;
80
+ salaryTimeframe: string | null;
81
+ isFeatured: boolean;
82
+ publishedAt: string | null;
83
+ expiresAt: string | null;
84
+ createdAt: string;
85
+ updatedAt: string;
86
+ description: string | null;
87
+ applicationUrl: string | null;
88
+ /** Canonical hierarchical permit selection. */
89
+ remotePermits: RemotePermit[];
90
+ /** Read-only — derived from `remotePermits`. */
91
+ remoteWorldwide: boolean | null;
92
+ /** Canonical hierarchical timezone selection. */
93
+ remoteTimezones: RemoteTimezone[];
94
+ /** Read-only — derived from `remoteTimezones`. */
95
+ remoteAllowedTzOffsets: number[];
96
+ /** Read-only — derived from `remotePermits`. */
97
+ remoteWorkPermitCountryCodes: string[];
98
+ /** Read-only — derived from `remotePermits`. */
99
+ remoteWorkPermitSubdivisionCodes: string[];
100
+ remoteSponsorship: 'yes' | 'no' | 'unknown';
101
+ educationRequirements: EducationRequirement[];
102
+ experienceMonths: number | null;
103
+ experienceInPlaceOfEducation: boolean | null;
104
+ inOfficePeriod: 'per_week' | 'per_month' | 'per_year' | null;
105
+ inOfficeFrequency: number | null;
106
+ company: JobCompany | null;
107
+ officeLocations: OfficeLocation[];
108
+ /** Resolved taxonomy — same `{slug,name}` shape as `PublicJobCard`; names joined server-side. */
109
+ categories: Array<{
110
+ slug: string;
111
+ name: string;
112
+ }>;
113
+ skills: Array<{
114
+ slug: string;
115
+ name: string;
116
+ }>;
117
+ /**
118
+ * Place ancestor chain (country → region → city) for the breadcrumb. Each
119
+ * `slug` is the English SOURCE slug — the key `taxonomy.places.resolve()`
120
+ * accepts. On a localized board it may differ from the canonical
121
+ * board-language URL slug (the board router 308-redirects the source slug to
122
+ * the canonical one); link via `/jobs/locations/:slug`.
123
+ */
124
+ placeHierarchy: Array<{
125
+ slug: string;
126
+ name: string;
127
+ }>;
128
+ links: {
129
+ public: string | null;
130
+ };
131
+ }
132
+ /**
133
+ * The slim listing card (ADR-0037 §1) returned by `jobs.list` / `jobs.search`
134
+ * / `companies.listJobs`. Full enrichment (description, officeLocations, the
135
+ * structured remote blocks, …) lives on `jobs.retrieve` (`PublicJob`).
136
+ * Transcribed from `serializeJobCard`.
137
+ */
138
+ interface PublicJobCard {
139
+ id: string;
140
+ object: 'job_card';
141
+ slug: string;
142
+ title: string;
143
+ publishedAt: string | null;
144
+ employmentType: string | null;
145
+ remoteOption: string | null;
146
+ /** Remote-region label for remote jobs (e.g. "Worldwide"); `null` otherwise. */
147
+ remoteLocationLabel: string | null;
148
+ salaryMin: number | null;
149
+ salaryMax: number | null;
150
+ salaryCurrency: string | null;
151
+ salaryTimeframe: string | null;
152
+ isFeatured: boolean;
153
+ locationLabel: string | null;
154
+ company: {
155
+ slug: string;
156
+ name: string;
157
+ logoUrl: string | null;
158
+ } | null;
159
+ categories: Array<{
160
+ slug: string;
161
+ name: string;
162
+ }>;
163
+ skills: Array<{
164
+ slug: string;
165
+ name: string;
166
+ }>;
167
+ links: {
168
+ public: string | null;
169
+ };
170
+ /**
171
+ * Long-form description (HTML), or `null`. Present ONLY when the list/search
172
+ * was called with `fields: '+description'` (the RSS feed's sparse-fieldset
173
+ * opt-in); `undefined`/absent on the default slim card.
174
+ */
175
+ description?: string | null;
176
+ }
177
+ /** Derived category/skill suggestion on the jobs browse list (ADR-0037 §8). */
178
+ interface RelatedSearch {
179
+ type: 'category' | 'skill';
180
+ slug: string;
181
+ term: string;
182
+ count: number;
183
+ }
184
+ /** The browse list envelope — `PublicJobCard`s + storefront fields + `relatedSearches`. */
185
+ interface JobCardListEnvelope extends ListEnvelope<PublicJobCard> {
186
+ relatedSearches?: RelatedSearch[];
187
+ }
188
+ /** The search envelope — `PublicJobCard`s + storefront fields. */
189
+ type JobCardSearchEnvelope = SearchEnvelope<PublicJobCard>;
190
+ type JobsListQuery = {
191
+ cursor?: string;
192
+ /** 1–100. */
193
+ limit?: number;
194
+ /** Storefront page offset; takes precedence over `cursor`. `offset + limit` ≤ 10,000. */
195
+ offset?: number;
196
+ /** Single or repeated (up to 10) — repeated params are OR-matched. */
197
+ companyId?: string | string[];
198
+ remoteOption?: RemoteOption | RemoteOption[];
199
+ employmentType?: EmploymentType | EmploymentType[];
200
+ seniority?: Seniority | Seniority[];
201
+ /** Place slug for a geo radius search; unresolvable slugs are ignored. */
202
+ location?: string;
203
+ /** Radius in km around `location` (10–250; default 50). */
204
+ radius?: number;
205
+ /** Category slug seed (the `/jobs/[keyword]` page) — server resolves it to the English source name; unresolvable → 404. */
206
+ category?: string;
207
+ /** Skill slug seed (the `/jobs/skills/[skill]` page) — server-resolved; unresolvable → 404. */
208
+ skill?: string;
209
+ /** Sparse fieldset (Medusa-style `+field`). Only `'+description'` is supported — adds `description` to each card. */
210
+ fields?: string;
211
+ };
212
+ type JobsSimilarQuery = {
213
+ /** How many similar jobs to return (1–20; default 5). */
214
+ limit?: number;
215
+ };
216
+ interface JobsSearchBody {
217
+ /** Free-text query, up to 200 characters. */
218
+ query?: string;
219
+ filters?: {
220
+ /** Up to 10 values each. */
221
+ companyId?: string[];
222
+ remoteOption?: RemoteOption[];
223
+ employmentType?: EmploymentType[];
224
+ seniority?: Seniority[];
225
+ /** ISO 8601 datetime bounds. */
226
+ publishedAt?: {
227
+ gte?: string;
228
+ lte?: string;
229
+ };
230
+ /** Place slug for a geo radius search. */
231
+ location?: string;
232
+ /** Radius in km around `location` (10–250; default 50). */
233
+ radius?: number;
234
+ };
235
+ cursor?: string;
236
+ /** 1–100. */
237
+ limit?: number;
238
+ /** Storefront page offset; takes precedence over `cursor`. */
239
+ offset?: number;
240
+ }
241
+
1
242
  type Awaitable<T> = T | Promise<T>;
2
243
  /**
3
244
  * Async token storage. The SDK reads the access token from storage on
@@ -151,7 +392,7 @@ declare function isConflict(e: unknown): e is BoardApiError;
151
392
  * constant because the package is platform-neutral and cannot read
152
393
  * package.json at runtime.
153
394
  */
154
- declare const SDK_VERSION = "1.0.0";
395
+ declare const SDK_VERSION = "1.1.0";
155
396
 
156
397
  interface BoardUser {
157
398
  id: string;
@@ -268,27 +509,6 @@ interface BlogSearchBody {
268
509
  limit?: number;
269
510
  }
270
511
 
271
- /**
272
- * Stripe-shaped success envelopes (`01-conventions.md` §5.1). The
273
- * server MAY add top-level fields; consumers MUST ignore unknown
274
- * fields.
275
- */
276
- interface ListEnvelope<T> {
277
- object: 'list';
278
- url: string;
279
- hasMore: boolean;
280
- /** `null` when `hasMore` is false — always present, never undefined. */
281
- nextCursor: string | null;
282
- data: T[];
283
- }
284
- interface SearchEnvelope<T> {
285
- object: 'search_result';
286
- url: string;
287
- hasMore: boolean;
288
- nextCursor: string | null;
289
- data: T[];
290
- }
291
-
292
512
  interface PublicCompany {
293
513
  id: string;
294
514
  object: 'public_company';
@@ -321,110 +541,6 @@ interface CompaniesSearchBody {
321
541
  limit?: number;
322
542
  }
323
543
 
324
- type RemoteOption = 'on_site' | 'hybrid' | 'remote';
325
- type EmploymentType = 'full_time' | 'part_time' | 'contract' | 'internship' | 'temporary' | 'volunteer' | 'other';
326
- type Seniority = 'entry_level' | 'associate' | 'mid_level' | 'senior' | 'lead' | 'principal' | 'director' | 'executive';
327
- type EducationRequirement = 'high_school' | 'associate_degree' | 'bachelor_degree' | 'professional_certificate' | 'postgraduate_degree' | 'no_requirements';
328
- interface OfficeLocation {
329
- countryCode: string | null;
330
- country: string | null;
331
- locality: string | null;
332
- city: string | null;
333
- region: string | null;
334
- regionCode: string | null;
335
- postalCode: string | null;
336
- displayName: string | null;
337
- }
338
- interface JobCompany {
339
- id: string;
340
- name: string | null;
341
- slug: string | null;
342
- logoUrl: string | null;
343
- website: string | null;
344
- }
345
- interface RemotePermit {
346
- type: string;
347
- value: string;
348
- }
349
- interface RemoteTimezone {
350
- type: string;
351
- value: string;
352
- plusMinus?: number;
353
- }
354
- interface PublicJob {
355
- id: string;
356
- object: 'public_job';
357
- title: string;
358
- slug: string | null;
359
- status: string;
360
- companyId: string | null;
361
- employmentType: string | null;
362
- remoteOption: string | null;
363
- seniority: string | null;
364
- salaryMin: number | null;
365
- salaryMax: number | null;
366
- salaryCurrency: string | null;
367
- salaryTimeframe: string | null;
368
- isFeatured: boolean;
369
- publishedAt: string | null;
370
- expiresAt: string | null;
371
- createdAt: string;
372
- updatedAt: string;
373
- description: string | null;
374
- applicationUrl: string | null;
375
- /** Canonical hierarchical permit selection. */
376
- remotePermits: RemotePermit[];
377
- /** Read-only — derived from `remotePermits`. */
378
- remoteWorldwide: boolean | null;
379
- /** Canonical hierarchical timezone selection. */
380
- remoteTimezones: RemoteTimezone[];
381
- /** Read-only — derived from `remoteTimezones`. */
382
- remoteAllowedTzOffsets: number[];
383
- /** Read-only — derived from `remotePermits`. */
384
- remoteWorkPermitCountryCodes: string[];
385
- /** Read-only — derived from `remotePermits`. */
386
- remoteWorkPermitSubdivisionCodes: string[];
387
- remoteSponsorship: 'yes' | 'no' | 'unknown';
388
- educationRequirements: EducationRequirement[];
389
- experienceMonths: number | null;
390
- experienceInPlaceOfEducation: boolean | null;
391
- inOfficePeriod: 'per_week' | 'per_month' | 'per_year' | null;
392
- inOfficeFrequency: number | null;
393
- company: JobCompany | null;
394
- officeLocations: OfficeLocation[];
395
- links: {
396
- public: string | null;
397
- };
398
- }
399
- type JobsListQuery = {
400
- cursor?: string;
401
- /** 1–100. */
402
- limit?: number;
403
- companyId?: string;
404
- remoteOption?: RemoteOption;
405
- employmentType?: EmploymentType;
406
- seniority?: Seniority;
407
- };
408
- interface JobsSearchBody {
409
- /** Free-text query, up to 200 characters. */
410
- query?: string;
411
- filters?: {
412
- /** Up to 10 values each. */
413
- companyId?: string[];
414
- remoteOption?: RemoteOption[];
415
- employmentType?: EmploymentType[];
416
- seniority?: Seniority[];
417
- /** ISO 8601 datetime bounds. */
418
- publishedAt?: {
419
- gte?: string;
420
- lte?: string;
421
- };
422
- };
423
- cursor?: string;
424
- /** 1–100. */
425
- limit?: number;
426
- }
427
-
428
544
  /**
429
545
  * The embedded `job` is the SAME `public_job` shape the anonymous jobs
430
546
  * list emits — saved rows and search rows render with one component.
@@ -446,6 +562,53 @@ interface SaveJobBody {
446
562
  jobId: string;
447
563
  }
448
564
 
565
+ interface TaxonomyGeo {
566
+ lat: number | null;
567
+ lng: number | null;
568
+ countryCode: string | null;
569
+ regionCode: string | null;
570
+ region: string | null;
571
+ city: string | null;
572
+ locality: string | null;
573
+ placeType: string | null;
574
+ }
575
+ /**
576
+ * Page-meta for a resolved taxonomy slug. `sourceSlug` is the immutable
577
+ * English search key; `canonicalSlug` is the board-language URL; `redirectTo`
578
+ * is the canonical slug to 308 to when the inbound slug isn't canonical (the
579
+ * host app emits the redirect — the SDK never navigates). `geo` is set for
580
+ * place resolutions only.
581
+ */
582
+ interface TaxonomyResolution {
583
+ object: 'taxonomy_resolution';
584
+ type: 'category' | 'skill' | 'place';
585
+ sourceSlug: string;
586
+ canonicalSlug: string;
587
+ displayName: string;
588
+ redirectTo: string | null;
589
+ geo: TaxonomyGeo | null;
590
+ }
591
+ /**
592
+ * A place in the board's locations directory (`taxonomy.places.list()` →
593
+ * `GET /places`), the data the `/jobs/locations/` index renders. `jobCount` is
594
+ * subtree-summed (a parent counts its descendants); `id`/`parentId` carry the
595
+ * hierarchy so consumers rebuild the same nested tree the hosted index shows.
596
+ */
597
+ interface PublicPlace {
598
+ object: 'place';
599
+ /** Stable place identity (locations-tree edge endpoint). */
600
+ id: string;
601
+ /** Parent place's `id`; `null` for a root place. */
602
+ parentId: string | null;
603
+ /** Public slug (links to `/jobs/locations/:slug`); `null` if unslugged. */
604
+ slug: string | null;
605
+ name: string;
606
+ placeType: string;
607
+ countryCode: string | null;
608
+ regionCode: string | null;
609
+ jobCount: number;
610
+ }
611
+
449
612
  interface CreateBoardClientOptions {
450
613
  baseUrl: string;
451
614
  /** Board identifier: `pk_…` key (provisioned default) | `boards_…` ID | slug. */
@@ -490,15 +653,16 @@ declare function createBoardClient(options: CreateBoardClientOptions): {
490
653
  */
491
654
  context(options?: FetchOptions): Promise<PublicBoard>;
492
655
  jobs: {
493
- list(query?: JobsListQuery, options?: FetchOptions): Promise<ListEnvelope<PublicJob>>;
656
+ list(query?: JobsListQuery, options?: FetchOptions): Promise<JobCardListEnvelope>;
494
657
  retrieve(jobSlug: string, query?: Record<string, never>, options?: FetchOptions): Promise<PublicJob>;
495
- search(body: JobsSearchBody, query?: Record<string, never>, options?: FetchOptions): Promise<SearchEnvelope<PublicJob>>;
658
+ search(body: JobsSearchBody, query?: Record<string, never>, options?: FetchOptions): Promise<JobCardSearchEnvelope>;
659
+ similar(jobSlug: string, query?: JobsSimilarQuery, options?: FetchOptions): Promise<ListEnvelope<PublicJobCard>>;
496
660
  };
497
661
  companies: {
498
662
  list(query?: CompaniesListQuery, options?: FetchOptions): Promise<ListEnvelope<PublicCompany>>;
499
663
  retrieve(companySlug: string, query?: Record<string, never>, options?: FetchOptions): Promise<PublicCompany>;
500
664
  search(body: CompaniesSearchBody, query?: Record<string, never>, options?: FetchOptions): Promise<SearchEnvelope<PublicCompany>>;
501
- listJobs(companySlug: string, query?: CompanyJobsListQuery, options?: FetchOptions): Promise<ListEnvelope<PublicJob>>;
665
+ listJobs(companySlug: string, query?: CompanyJobsListQuery, options?: FetchOptions): Promise<JobCardListEnvelope>;
502
666
  };
503
667
  blog: {
504
668
  posts: {
@@ -532,7 +696,19 @@ declare function createBoardClient(options: CreateBoardClientOptions): {
532
696
  unsave(jobId: string, query?: Record<string, never>, options?: FetchOptions): Promise<void>;
533
697
  };
534
698
  };
699
+ taxonomy: {
700
+ categories: {
701
+ resolve(slug: string, options?: FetchOptions): Promise<TaxonomyResolution>;
702
+ };
703
+ skills: {
704
+ resolve(slug: string, options?: FetchOptions): Promise<TaxonomyResolution>;
705
+ };
706
+ places: {
707
+ list(options?: FetchOptions): Promise<ListEnvelope<PublicPlace>>;
708
+ resolve(slug: string, options?: FetchOptions): Promise<TaxonomyResolution>;
709
+ };
710
+ };
535
711
  };
536
712
  type BoardSdk = ReturnType<typeof createBoardClient>;
537
713
 
538
- export { ACCESS_TOKEN_KEY, type Awaitable, type BlogAuthorEmbed, type BlogPostsListQuery, type BlogSearchBody, type BlogTagEmbed, BoardApiError, type BoardAuthSession, BoardClient, type BoardRequest, type BoardSdk, type BoardUser, type CompaniesListQuery, type CompaniesSearchBody, type CompanyJobsListQuery, type CreateBoardClientOptions, type CustomStorage, type EducationRequirement, type EmploymentType, type FetchOptions, type ForgotPasswordBody, 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, REFRESH_TOKEN_KEY, type RefreshBody, type RegisterBody, type RemoteOption, type RemotePermit, type RemoteTimezone, type ResetPasswordBody, SDK_VERSION, type SaveJobBody, type SavedJob, type SavedJobsListQuery, type SearchEnvelope, type Seniority, type StorageMode, type VerifyEmailBody, createBoardClient, isBoardApiError, isConflict, isForbidden, isNotFound, isRateLimited, isUnauthorized, isValidationError };
714
+ export { ACCESS_TOKEN_KEY, type Awaitable, type BlogAuthorEmbed, type BlogPostsListQuery, type BlogSearchBody, type BlogTagEmbed, BoardApiError, type BoardAuthSession, BoardClient, type BoardRequest, type BoardSdk, 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 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 };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,244 @@
1
+ /**
2
+ * Stripe-shaped success envelopes (`01-conventions.md` §5.1). The
3
+ * server MAY add top-level fields; consumers MUST ignore unknown
4
+ * fields.
5
+ */
6
+ /**
7
+ * Medusa-style storefront pagination fields (ADR-0037 §7), populated only by
8
+ * the board jobs catalog reads (browse / search / company-jobs); absent on
9
+ * every other list/search. `nextCursor` is preserved alongside (offset-encoded).
10
+ */
11
+ interface StorefrontPagination {
12
+ /** Total matching results ("X jobs"). */
13
+ count?: number;
14
+ /** The page size used for this response. */
15
+ limit?: number;
16
+ /** Number of items skipped before this page. */
17
+ offset?: number;
18
+ /** Items hidden behind the candidate paywall for the current viewer; absent/0 when entitled. */
19
+ gatedCount?: number;
20
+ }
21
+ interface ListEnvelope<T> extends StorefrontPagination {
22
+ object: 'list';
23
+ url: string;
24
+ hasMore: boolean;
25
+ /** `null` when `hasMore` is false — always present, never undefined. */
26
+ nextCursor: string | null;
27
+ data: T[];
28
+ }
29
+ interface SearchEnvelope<T> extends StorefrontPagination {
30
+ object: 'search_result';
31
+ url: string;
32
+ hasMore: boolean;
33
+ nextCursor: string | null;
34
+ data: T[];
35
+ }
36
+
37
+ type RemoteOption = 'on_site' | 'hybrid' | 'remote';
38
+ type EmploymentType = 'full_time' | 'part_time' | 'contract' | 'internship' | 'temporary' | 'volunteer' | 'other';
39
+ type Seniority = 'entry_level' | 'associate' | 'mid_level' | 'senior' | 'lead' | 'principal' | 'director' | 'executive';
40
+ type EducationRequirement = 'high_school' | 'associate_degree' | 'bachelor_degree' | 'professional_certificate' | 'postgraduate_degree' | 'no_requirements';
41
+ interface OfficeLocation {
42
+ countryCode: string | null;
43
+ country: string | null;
44
+ locality: string | null;
45
+ city: string | null;
46
+ region: string | null;
47
+ regionCode: string | null;
48
+ postalCode: string | null;
49
+ displayName: string | null;
50
+ }
51
+ interface JobCompany {
52
+ id: string;
53
+ name: string | null;
54
+ slug: string | null;
55
+ logoUrl: string | null;
56
+ website: string | null;
57
+ }
58
+ interface RemotePermit {
59
+ type: string;
60
+ value: string;
61
+ }
62
+ interface RemoteTimezone {
63
+ type: string;
64
+ value: string;
65
+ plusMinus?: number;
66
+ }
67
+ interface PublicJob {
68
+ id: string;
69
+ object: 'public_job';
70
+ title: string;
71
+ slug: string | null;
72
+ status: string;
73
+ companyId: string | null;
74
+ employmentType: string | null;
75
+ remoteOption: string | null;
76
+ seniority: string | null;
77
+ salaryMin: number | null;
78
+ salaryMax: number | null;
79
+ salaryCurrency: string | null;
80
+ salaryTimeframe: string | null;
81
+ isFeatured: boolean;
82
+ publishedAt: string | null;
83
+ expiresAt: string | null;
84
+ createdAt: string;
85
+ updatedAt: string;
86
+ description: string | null;
87
+ applicationUrl: string | null;
88
+ /** Canonical hierarchical permit selection. */
89
+ remotePermits: RemotePermit[];
90
+ /** Read-only — derived from `remotePermits`. */
91
+ remoteWorldwide: boolean | null;
92
+ /** Canonical hierarchical timezone selection. */
93
+ remoteTimezones: RemoteTimezone[];
94
+ /** Read-only — derived from `remoteTimezones`. */
95
+ remoteAllowedTzOffsets: number[];
96
+ /** Read-only — derived from `remotePermits`. */
97
+ remoteWorkPermitCountryCodes: string[];
98
+ /** Read-only — derived from `remotePermits`. */
99
+ remoteWorkPermitSubdivisionCodes: string[];
100
+ remoteSponsorship: 'yes' | 'no' | 'unknown';
101
+ educationRequirements: EducationRequirement[];
102
+ experienceMonths: number | null;
103
+ experienceInPlaceOfEducation: boolean | null;
104
+ inOfficePeriod: 'per_week' | 'per_month' | 'per_year' | null;
105
+ inOfficeFrequency: number | null;
106
+ company: JobCompany | null;
107
+ officeLocations: OfficeLocation[];
108
+ /** Resolved taxonomy — same `{slug,name}` shape as `PublicJobCard`; names joined server-side. */
109
+ categories: Array<{
110
+ slug: string;
111
+ name: string;
112
+ }>;
113
+ skills: Array<{
114
+ slug: string;
115
+ name: string;
116
+ }>;
117
+ /**
118
+ * Place ancestor chain (country → region → city) for the breadcrumb. Each
119
+ * `slug` is the English SOURCE slug — the key `taxonomy.places.resolve()`
120
+ * accepts. On a localized board it may differ from the canonical
121
+ * board-language URL slug (the board router 308-redirects the source slug to
122
+ * the canonical one); link via `/jobs/locations/:slug`.
123
+ */
124
+ placeHierarchy: Array<{
125
+ slug: string;
126
+ name: string;
127
+ }>;
128
+ links: {
129
+ public: string | null;
130
+ };
131
+ }
132
+ /**
133
+ * The slim listing card (ADR-0037 §1) returned by `jobs.list` / `jobs.search`
134
+ * / `companies.listJobs`. Full enrichment (description, officeLocations, the
135
+ * structured remote blocks, …) lives on `jobs.retrieve` (`PublicJob`).
136
+ * Transcribed from `serializeJobCard`.
137
+ */
138
+ interface PublicJobCard {
139
+ id: string;
140
+ object: 'job_card';
141
+ slug: string;
142
+ title: string;
143
+ publishedAt: string | null;
144
+ employmentType: string | null;
145
+ remoteOption: string | null;
146
+ /** Remote-region label for remote jobs (e.g. "Worldwide"); `null` otherwise. */
147
+ remoteLocationLabel: string | null;
148
+ salaryMin: number | null;
149
+ salaryMax: number | null;
150
+ salaryCurrency: string | null;
151
+ salaryTimeframe: string | null;
152
+ isFeatured: boolean;
153
+ locationLabel: string | null;
154
+ company: {
155
+ slug: string;
156
+ name: string;
157
+ logoUrl: string | null;
158
+ } | null;
159
+ categories: Array<{
160
+ slug: string;
161
+ name: string;
162
+ }>;
163
+ skills: Array<{
164
+ slug: string;
165
+ name: string;
166
+ }>;
167
+ links: {
168
+ public: string | null;
169
+ };
170
+ /**
171
+ * Long-form description (HTML), or `null`. Present ONLY when the list/search
172
+ * was called with `fields: '+description'` (the RSS feed's sparse-fieldset
173
+ * opt-in); `undefined`/absent on the default slim card.
174
+ */
175
+ description?: string | null;
176
+ }
177
+ /** Derived category/skill suggestion on the jobs browse list (ADR-0037 §8). */
178
+ interface RelatedSearch {
179
+ type: 'category' | 'skill';
180
+ slug: string;
181
+ term: string;
182
+ count: number;
183
+ }
184
+ /** The browse list envelope — `PublicJobCard`s + storefront fields + `relatedSearches`. */
185
+ interface JobCardListEnvelope extends ListEnvelope<PublicJobCard> {
186
+ relatedSearches?: RelatedSearch[];
187
+ }
188
+ /** The search envelope — `PublicJobCard`s + storefront fields. */
189
+ type JobCardSearchEnvelope = SearchEnvelope<PublicJobCard>;
190
+ type JobsListQuery = {
191
+ cursor?: string;
192
+ /** 1–100. */
193
+ limit?: number;
194
+ /** Storefront page offset; takes precedence over `cursor`. `offset + limit` ≤ 10,000. */
195
+ offset?: number;
196
+ /** Single or repeated (up to 10) — repeated params are OR-matched. */
197
+ companyId?: string | string[];
198
+ remoteOption?: RemoteOption | RemoteOption[];
199
+ employmentType?: EmploymentType | EmploymentType[];
200
+ seniority?: Seniority | Seniority[];
201
+ /** Place slug for a geo radius search; unresolvable slugs are ignored. */
202
+ location?: string;
203
+ /** Radius in km around `location` (10–250; default 50). */
204
+ radius?: number;
205
+ /** Category slug seed (the `/jobs/[keyword]` page) — server resolves it to the English source name; unresolvable → 404. */
206
+ category?: string;
207
+ /** Skill slug seed (the `/jobs/skills/[skill]` page) — server-resolved; unresolvable → 404. */
208
+ skill?: string;
209
+ /** Sparse fieldset (Medusa-style `+field`). Only `'+description'` is supported — adds `description` to each card. */
210
+ fields?: string;
211
+ };
212
+ type JobsSimilarQuery = {
213
+ /** How many similar jobs to return (1–20; default 5). */
214
+ limit?: number;
215
+ };
216
+ interface JobsSearchBody {
217
+ /** Free-text query, up to 200 characters. */
218
+ query?: string;
219
+ filters?: {
220
+ /** Up to 10 values each. */
221
+ companyId?: string[];
222
+ remoteOption?: RemoteOption[];
223
+ employmentType?: EmploymentType[];
224
+ seniority?: Seniority[];
225
+ /** ISO 8601 datetime bounds. */
226
+ publishedAt?: {
227
+ gte?: string;
228
+ lte?: string;
229
+ };
230
+ /** Place slug for a geo radius search. */
231
+ location?: string;
232
+ /** Radius in km around `location` (10–250; default 50). */
233
+ radius?: number;
234
+ };
235
+ cursor?: string;
236
+ /** 1–100. */
237
+ limit?: number;
238
+ /** Storefront page offset; takes precedence over `cursor`. */
239
+ offset?: number;
240
+ }
241
+
1
242
  type Awaitable<T> = T | Promise<T>;
2
243
  /**
3
244
  * Async token storage. The SDK reads the access token from storage on
@@ -151,7 +392,7 @@ declare function isConflict(e: unknown): e is BoardApiError;
151
392
  * constant because the package is platform-neutral and cannot read
152
393
  * package.json at runtime.
153
394
  */
154
- declare const SDK_VERSION = "1.0.0";
395
+ declare const SDK_VERSION = "1.1.0";
155
396
 
156
397
  interface BoardUser {
157
398
  id: string;
@@ -268,27 +509,6 @@ interface BlogSearchBody {
268
509
  limit?: number;
269
510
  }
270
511
 
271
- /**
272
- * Stripe-shaped success envelopes (`01-conventions.md` §5.1). The
273
- * server MAY add top-level fields; consumers MUST ignore unknown
274
- * fields.
275
- */
276
- interface ListEnvelope<T> {
277
- object: 'list';
278
- url: string;
279
- hasMore: boolean;
280
- /** `null` when `hasMore` is false — always present, never undefined. */
281
- nextCursor: string | null;
282
- data: T[];
283
- }
284
- interface SearchEnvelope<T> {
285
- object: 'search_result';
286
- url: string;
287
- hasMore: boolean;
288
- nextCursor: string | null;
289
- data: T[];
290
- }
291
-
292
512
  interface PublicCompany {
293
513
  id: string;
294
514
  object: 'public_company';
@@ -321,110 +541,6 @@ interface CompaniesSearchBody {
321
541
  limit?: number;
322
542
  }
323
543
 
324
- type RemoteOption = 'on_site' | 'hybrid' | 'remote';
325
- type EmploymentType = 'full_time' | 'part_time' | 'contract' | 'internship' | 'temporary' | 'volunteer' | 'other';
326
- type Seniority = 'entry_level' | 'associate' | 'mid_level' | 'senior' | 'lead' | 'principal' | 'director' | 'executive';
327
- type EducationRequirement = 'high_school' | 'associate_degree' | 'bachelor_degree' | 'professional_certificate' | 'postgraduate_degree' | 'no_requirements';
328
- interface OfficeLocation {
329
- countryCode: string | null;
330
- country: string | null;
331
- locality: string | null;
332
- city: string | null;
333
- region: string | null;
334
- regionCode: string | null;
335
- postalCode: string | null;
336
- displayName: string | null;
337
- }
338
- interface JobCompany {
339
- id: string;
340
- name: string | null;
341
- slug: string | null;
342
- logoUrl: string | null;
343
- website: string | null;
344
- }
345
- interface RemotePermit {
346
- type: string;
347
- value: string;
348
- }
349
- interface RemoteTimezone {
350
- type: string;
351
- value: string;
352
- plusMinus?: number;
353
- }
354
- interface PublicJob {
355
- id: string;
356
- object: 'public_job';
357
- title: string;
358
- slug: string | null;
359
- status: string;
360
- companyId: string | null;
361
- employmentType: string | null;
362
- remoteOption: string | null;
363
- seniority: string | null;
364
- salaryMin: number | null;
365
- salaryMax: number | null;
366
- salaryCurrency: string | null;
367
- salaryTimeframe: string | null;
368
- isFeatured: boolean;
369
- publishedAt: string | null;
370
- expiresAt: string | null;
371
- createdAt: string;
372
- updatedAt: string;
373
- description: string | null;
374
- applicationUrl: string | null;
375
- /** Canonical hierarchical permit selection. */
376
- remotePermits: RemotePermit[];
377
- /** Read-only — derived from `remotePermits`. */
378
- remoteWorldwide: boolean | null;
379
- /** Canonical hierarchical timezone selection. */
380
- remoteTimezones: RemoteTimezone[];
381
- /** Read-only — derived from `remoteTimezones`. */
382
- remoteAllowedTzOffsets: number[];
383
- /** Read-only — derived from `remotePermits`. */
384
- remoteWorkPermitCountryCodes: string[];
385
- /** Read-only — derived from `remotePermits`. */
386
- remoteWorkPermitSubdivisionCodes: string[];
387
- remoteSponsorship: 'yes' | 'no' | 'unknown';
388
- educationRequirements: EducationRequirement[];
389
- experienceMonths: number | null;
390
- experienceInPlaceOfEducation: boolean | null;
391
- inOfficePeriod: 'per_week' | 'per_month' | 'per_year' | null;
392
- inOfficeFrequency: number | null;
393
- company: JobCompany | null;
394
- officeLocations: OfficeLocation[];
395
- links: {
396
- public: string | null;
397
- };
398
- }
399
- type JobsListQuery = {
400
- cursor?: string;
401
- /** 1–100. */
402
- limit?: number;
403
- companyId?: string;
404
- remoteOption?: RemoteOption;
405
- employmentType?: EmploymentType;
406
- seniority?: Seniority;
407
- };
408
- interface JobsSearchBody {
409
- /** Free-text query, up to 200 characters. */
410
- query?: string;
411
- filters?: {
412
- /** Up to 10 values each. */
413
- companyId?: string[];
414
- remoteOption?: RemoteOption[];
415
- employmentType?: EmploymentType[];
416
- seniority?: Seniority[];
417
- /** ISO 8601 datetime bounds. */
418
- publishedAt?: {
419
- gte?: string;
420
- lte?: string;
421
- };
422
- };
423
- cursor?: string;
424
- /** 1–100. */
425
- limit?: number;
426
- }
427
-
428
544
  /**
429
545
  * The embedded `job` is the SAME `public_job` shape the anonymous jobs
430
546
  * list emits — saved rows and search rows render with one component.
@@ -446,6 +562,53 @@ interface SaveJobBody {
446
562
  jobId: string;
447
563
  }
448
564
 
565
+ interface TaxonomyGeo {
566
+ lat: number | null;
567
+ lng: number | null;
568
+ countryCode: string | null;
569
+ regionCode: string | null;
570
+ region: string | null;
571
+ city: string | null;
572
+ locality: string | null;
573
+ placeType: string | null;
574
+ }
575
+ /**
576
+ * Page-meta for a resolved taxonomy slug. `sourceSlug` is the immutable
577
+ * English search key; `canonicalSlug` is the board-language URL; `redirectTo`
578
+ * is the canonical slug to 308 to when the inbound slug isn't canonical (the
579
+ * host app emits the redirect — the SDK never navigates). `geo` is set for
580
+ * place resolutions only.
581
+ */
582
+ interface TaxonomyResolution {
583
+ object: 'taxonomy_resolution';
584
+ type: 'category' | 'skill' | 'place';
585
+ sourceSlug: string;
586
+ canonicalSlug: string;
587
+ displayName: string;
588
+ redirectTo: string | null;
589
+ geo: TaxonomyGeo | null;
590
+ }
591
+ /**
592
+ * A place in the board's locations directory (`taxonomy.places.list()` →
593
+ * `GET /places`), the data the `/jobs/locations/` index renders. `jobCount` is
594
+ * subtree-summed (a parent counts its descendants); `id`/`parentId` carry the
595
+ * hierarchy so consumers rebuild the same nested tree the hosted index shows.
596
+ */
597
+ interface PublicPlace {
598
+ object: 'place';
599
+ /** Stable place identity (locations-tree edge endpoint). */
600
+ id: string;
601
+ /** Parent place's `id`; `null` for a root place. */
602
+ parentId: string | null;
603
+ /** Public slug (links to `/jobs/locations/:slug`); `null` if unslugged. */
604
+ slug: string | null;
605
+ name: string;
606
+ placeType: string;
607
+ countryCode: string | null;
608
+ regionCode: string | null;
609
+ jobCount: number;
610
+ }
611
+
449
612
  interface CreateBoardClientOptions {
450
613
  baseUrl: string;
451
614
  /** Board identifier: `pk_…` key (provisioned default) | `boards_…` ID | slug. */
@@ -490,15 +653,16 @@ declare function createBoardClient(options: CreateBoardClientOptions): {
490
653
  */
491
654
  context(options?: FetchOptions): Promise<PublicBoard>;
492
655
  jobs: {
493
- list(query?: JobsListQuery, options?: FetchOptions): Promise<ListEnvelope<PublicJob>>;
656
+ list(query?: JobsListQuery, options?: FetchOptions): Promise<JobCardListEnvelope>;
494
657
  retrieve(jobSlug: string, query?: Record<string, never>, options?: FetchOptions): Promise<PublicJob>;
495
- search(body: JobsSearchBody, query?: Record<string, never>, options?: FetchOptions): Promise<SearchEnvelope<PublicJob>>;
658
+ search(body: JobsSearchBody, query?: Record<string, never>, options?: FetchOptions): Promise<JobCardSearchEnvelope>;
659
+ similar(jobSlug: string, query?: JobsSimilarQuery, options?: FetchOptions): Promise<ListEnvelope<PublicJobCard>>;
496
660
  };
497
661
  companies: {
498
662
  list(query?: CompaniesListQuery, options?: FetchOptions): Promise<ListEnvelope<PublicCompany>>;
499
663
  retrieve(companySlug: string, query?: Record<string, never>, options?: FetchOptions): Promise<PublicCompany>;
500
664
  search(body: CompaniesSearchBody, query?: Record<string, never>, options?: FetchOptions): Promise<SearchEnvelope<PublicCompany>>;
501
- listJobs(companySlug: string, query?: CompanyJobsListQuery, options?: FetchOptions): Promise<ListEnvelope<PublicJob>>;
665
+ listJobs(companySlug: string, query?: CompanyJobsListQuery, options?: FetchOptions): Promise<JobCardListEnvelope>;
502
666
  };
503
667
  blog: {
504
668
  posts: {
@@ -532,7 +696,19 @@ declare function createBoardClient(options: CreateBoardClientOptions): {
532
696
  unsave(jobId: string, query?: Record<string, never>, options?: FetchOptions): Promise<void>;
533
697
  };
534
698
  };
699
+ taxonomy: {
700
+ categories: {
701
+ resolve(slug: string, options?: FetchOptions): Promise<TaxonomyResolution>;
702
+ };
703
+ skills: {
704
+ resolve(slug: string, options?: FetchOptions): Promise<TaxonomyResolution>;
705
+ };
706
+ places: {
707
+ list(options?: FetchOptions): Promise<ListEnvelope<PublicPlace>>;
708
+ resolve(slug: string, options?: FetchOptions): Promise<TaxonomyResolution>;
709
+ };
710
+ };
535
711
  };
536
712
  type BoardSdk = ReturnType<typeof createBoardClient>;
537
713
 
538
- export { ACCESS_TOKEN_KEY, type Awaitable, type BlogAuthorEmbed, type BlogPostsListQuery, type BlogSearchBody, type BlogTagEmbed, BoardApiError, type BoardAuthSession, BoardClient, type BoardRequest, type BoardSdk, type BoardUser, type CompaniesListQuery, type CompaniesSearchBody, type CompanyJobsListQuery, type CreateBoardClientOptions, type CustomStorage, type EducationRequirement, type EmploymentType, type FetchOptions, type ForgotPasswordBody, 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, REFRESH_TOKEN_KEY, type RefreshBody, type RegisterBody, type RemoteOption, type RemotePermit, type RemoteTimezone, type ResetPasswordBody, SDK_VERSION, type SaveJobBody, type SavedJob, type SavedJobsListQuery, type SearchEnvelope, type Seniority, type StorageMode, type VerifyEmailBody, createBoardClient, isBoardApiError, isConflict, isForbidden, isNotFound, isRateLimited, isUnauthorized, isValidationError };
714
+ export { ACCESS_TOKEN_KEY, type Awaitable, type BlogAuthorEmbed, type BlogPostsListQuery, type BlogSearchBody, type BlogTagEmbed, BoardApiError, type BoardAuthSession, BoardClient, type BoardRequest, type BoardSdk, 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 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 };
package/dist/index.js CHANGED
@@ -143,7 +143,7 @@ async function clearSession(storage) {
143
143
  }
144
144
 
145
145
  // src/version.ts
146
- var SDK_VERSION = "1.0.0";
146
+ var SDK_VERSION = "1.1.0";
147
147
 
148
148
  // src/client.ts
149
149
  function isRawBody(body) {
@@ -544,6 +544,23 @@ function jobsNamespace(client) {
544
544
  body,
545
545
  query
546
546
  });
547
+ },
548
+ /**
549
+ * List jobs similar to one job — the same ranking that powers the
550
+ * on-page similar-jobs rail. Returns up to `limit` slim cards (default
551
+ * 5), excluding the job itself and any role at the same company.
552
+ *
553
+ * @example
554
+ * const { data } = await board.jobs.similar('senior-chef', { limit: 5 });
555
+ */
556
+ similar(jobSlug, query, options) {
557
+ return client.fetch(
558
+ `/jobs/${encodeURIComponent(jobSlug)}/similar`,
559
+ {
560
+ ...options,
561
+ query
562
+ }
563
+ );
547
564
  }
548
565
  };
549
566
  }
@@ -608,6 +625,35 @@ function meNamespace(client) {
608
625
  };
609
626
  }
610
627
 
628
+ // src/namespaces/taxonomy.ts
629
+ function taxonomyResolver(client, kind) {
630
+ return {
631
+ /**
632
+ * Resolve a (board-language or English) taxonomy slug to its page-meta.
633
+ * Rejects with a `not_found` `BoardApiError` when the slug doesn't resolve.
634
+ */
635
+ resolve(slug, options) {
636
+ return client.fetch(
637
+ `/${kind}/${encodeURIComponent(slug)}`,
638
+ options
639
+ );
640
+ }
641
+ };
642
+ }
643
+ function taxonomyNamespace(client) {
644
+ return {
645
+ categories: taxonomyResolver(client, "categories"),
646
+ skills: taxonomyResolver(client, "skills"),
647
+ places: {
648
+ ...taxonomyResolver(client, "places"),
649
+ /** List every place used by a published job, with its live job count. */
650
+ list(options) {
651
+ return client.fetch("/places", options);
652
+ }
653
+ }
654
+ };
655
+ }
656
+
611
657
  // src/index.ts
612
658
  function createBoardClient(options) {
613
659
  const client = new BoardClient({
@@ -639,6 +685,7 @@ function createBoardClient(options) {
639
685
  companies: companiesNamespace(client),
640
686
  blog: blogNamespace(client),
641
687
  auth: authNamespace(client),
642
- me: meNamespace(client)
688
+ me: meNamespace(client),
689
+ taxonomy: taxonomyNamespace(client)
643
690
  };
644
691
  }
package/dist/index.mjs CHANGED
@@ -105,7 +105,7 @@ async function clearSession(storage) {
105
105
  }
106
106
 
107
107
  // src/version.ts
108
- var SDK_VERSION = "1.0.0";
108
+ var SDK_VERSION = "1.1.0";
109
109
 
110
110
  // src/client.ts
111
111
  function isRawBody(body) {
@@ -506,6 +506,23 @@ function jobsNamespace(client) {
506
506
  body,
507
507
  query
508
508
  });
509
+ },
510
+ /**
511
+ * List jobs similar to one job — the same ranking that powers the
512
+ * on-page similar-jobs rail. Returns up to `limit` slim cards (default
513
+ * 5), excluding the job itself and any role at the same company.
514
+ *
515
+ * @example
516
+ * const { data } = await board.jobs.similar('senior-chef', { limit: 5 });
517
+ */
518
+ similar(jobSlug, query, options) {
519
+ return client.fetch(
520
+ `/jobs/${encodeURIComponent(jobSlug)}/similar`,
521
+ {
522
+ ...options,
523
+ query
524
+ }
525
+ );
509
526
  }
510
527
  };
511
528
  }
@@ -570,6 +587,35 @@ function meNamespace(client) {
570
587
  };
571
588
  }
572
589
 
590
+ // src/namespaces/taxonomy.ts
591
+ function taxonomyResolver(client, kind) {
592
+ return {
593
+ /**
594
+ * Resolve a (board-language or English) taxonomy slug to its page-meta.
595
+ * Rejects with a `not_found` `BoardApiError` when the slug doesn't resolve.
596
+ */
597
+ resolve(slug, options) {
598
+ return client.fetch(
599
+ `/${kind}/${encodeURIComponent(slug)}`,
600
+ options
601
+ );
602
+ }
603
+ };
604
+ }
605
+ function taxonomyNamespace(client) {
606
+ return {
607
+ categories: taxonomyResolver(client, "categories"),
608
+ skills: taxonomyResolver(client, "skills"),
609
+ places: {
610
+ ...taxonomyResolver(client, "places"),
611
+ /** List every place used by a published job, with its live job count. */
612
+ list(options) {
613
+ return client.fetch("/places", options);
614
+ }
615
+ }
616
+ };
617
+ }
618
+
573
619
  // src/index.ts
574
620
  function createBoardClient(options) {
575
621
  const client = new BoardClient({
@@ -601,7 +647,8 @@ function createBoardClient(options) {
601
647
  companies: companiesNamespace(client),
602
648
  blog: blogNamespace(client),
603
649
  auth: authNamespace(client),
604
- me: meNamespace(client)
650
+ me: meNamespace(client),
651
+ taxonomy: taxonomyNamespace(client)
605
652
  };
606
653
  }
607
654
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cavuno/board",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Typed isomorphic client for the Cavuno Board API",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -33,17 +33,18 @@
33
33
  "publishConfig": {
34
34
  "access": "public"
35
35
  },
36
- "devDependencies": {
37
- "@types/node": "24.9.1",
38
- "tsup": "^8.4.0",
39
- "vitest": "^2.1.9",
40
- "@kit/tsconfig": "0.1.0"
41
- },
42
36
  "scripts": {
43
37
  "build": "tsup",
44
38
  "clean": "git clean -xdf .turbo dist node_modules",
45
39
  "typecheck": "tsgo --noEmit",
46
40
  "test": "vitest run",
47
- "assert-publish-target": "node -e \"const p=require('./package.json'); if(p.name!=='@cavuno/board'){throw new Error('Refusing to publish: package.json name is '+p.name+', expected @cavuno/board')}; if(p.private){throw new Error('Refusing to publish: package.json has private:true')}\""
41
+ "assert-publish-target": "node -e \"const p=require('./package.json'); if(p.name!=='@cavuno/board'){throw new Error('Refusing to publish: package.json name is '+p.name+', expected @cavuno/board')}; if(p.private){throw new Error('Refusing to publish: package.json has private:true')}\"",
42
+ "prepublishOnly": "pnpm run assert-publish-target && pnpm run build"
43
+ },
44
+ "devDependencies": {
45
+ "@kit/tsconfig": "workspace:*",
46
+ "@types/node": "catalog:",
47
+ "tsup": "^8.4.0",
48
+ "vitest": "^2.1.9"
48
49
  }
49
- }
50
+ }