@cavuno/board 1.25.0 → 1.27.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/README.md +88 -0
- package/dist/bin.mjs +67 -34
- package/dist/filters.d.mts +55 -0
- package/dist/filters.d.ts +55 -0
- package/dist/filters.js +193 -0
- package/dist/filters.mjs +170 -0
- package/dist/format.d.mts +240 -0
- package/dist/format.d.ts +240 -0
- package/dist/format.js +453 -0
- package/dist/format.mjs +430 -0
- package/dist/index.d.mts +81 -3965
- package/dist/index.d.ts +81 -3965
- package/dist/index.js +154 -7
- package/dist/index.mjs +151 -4
- package/dist/jobs-DK5mPBgq.d.mts +3836 -0
- package/dist/jobs-DK5mPBgq.d.ts +3836 -0
- package/dist/salaries-CXt6Vkrp.d.ts +130 -0
- package/dist/salaries-CrJsaZe6.d.mts +130 -0
- package/dist/seo.d.mts +288 -0
- package/dist/seo.d.ts +288 -0
- package/dist/seo.js +1102 -0
- package/dist/seo.mjs +1079 -0
- package/dist/sitemap.d.mts +63 -0
- package/dist/sitemap.d.ts +63 -0
- package/dist/sitemap.js +353 -0
- package/dist/sitemap.mjs +330 -0
- package/dist/theme.d.mts +63 -0
- package/dist/theme.d.ts +63 -0
- package/dist/theme.js +149 -0
- package/dist/theme.mjs +128 -0
- package/package.json +57 -2
- package/skills/cavuno-board-account/SKILL.md +147 -0
- package/skills/cavuno-board-applications/SKILL.md +110 -0
- package/skills/cavuno-board-blog/SKILL.md +99 -0
- package/skills/cavuno-board-companies/SKILL.md +99 -0
- package/skills/cavuno-board-filters/SKILL.md +89 -0
- package/skills/cavuno-board-format/SKILL.md +104 -0
- package/skills/cavuno-board-job-alerts/SKILL.md +115 -0
- package/skills/cavuno-board-job-posting/SKILL.md +130 -0
- package/skills/cavuno-board-jobs/SKILL.md +15 -0
- package/skills/cavuno-board-messaging/SKILL.md +121 -0
- package/skills/cavuno-board-paywall/SKILL.md +108 -0
- package/skills/cavuno-board-salaries/SKILL.md +87 -0
- package/skills/cavuno-board-seo/SKILL.md +180 -0
- package/skills/cavuno-board-setup/SKILL.md +26 -2
- package/skills/cavuno-board-sitemap/SKILL.md +147 -0
- package/skills/cavuno-board-smoke-test/SKILL.md +84 -0
- package/skills/cavuno-board-theme/SKILL.md +69 -0
- package/skills/manifest.json +106 -1
package/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# @cavuno/board
|
|
2
|
+
|
|
3
|
+
Typed, isomorphic client for the [Cavuno](https://cavuno.com) Board API —
|
|
4
|
+
the headless toolkit for building fully-custom job-board frontends. Zero
|
|
5
|
+
dependencies; runs in the browser, Node ≥ 20, and Cloudflare Workers.
|
|
6
|
+
|
|
7
|
+
You bring the framework and own the layout; the SDK brings the job board:
|
|
8
|
+
jobs and search, companies, salaries, blog, board-user auth, saved jobs,
|
|
9
|
+
applications, job alerts, messaging, employer self-service, checkout, and
|
|
10
|
+
the candidate paywall — every method typed from the API's own OpenAPI
|
|
11
|
+
contract.
|
|
12
|
+
|
|
13
|
+
## Building with a coding agent
|
|
14
|
+
|
|
15
|
+
The package ships an agent skill corpus that teaches Claude Code (or any
|
|
16
|
+
SKILL.md-compatible agent) how to wire a board correctly — client setup,
|
|
17
|
+
auth and session ownership, pagination, gating, error handling, and a
|
|
18
|
+
runtime smoke test:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @cavuno/board
|
|
22
|
+
npx cavuno-board setup # copies version-matched skills into .claude/skills and/or .agents/skills
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Then ask your agent: *"set up my Cavuno board"* — it reads the
|
|
26
|
+
`cavuno-board-setup` skill and works surface by surface. Because the
|
|
27
|
+
skills live in your project and match your installed version, the agent
|
|
28
|
+
never works from stale docs.
|
|
29
|
+
|
|
30
|
+
## Quick start (by hand)
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { createBoardClient } from '@cavuno/board';
|
|
34
|
+
|
|
35
|
+
const board = createBoardClient({
|
|
36
|
+
baseUrl: process.env.PUBLIC_CAVUNO_API_URL!, // https://api.cavuno.com
|
|
37
|
+
board: process.env.PUBLIC_CAVUNO_BOARD!, // pk_… publishable key
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const { name, theme, features } = await board.context();
|
|
41
|
+
const page = await board.jobs.list({ limit: 20 });
|
|
42
|
+
const job = await board.jobs.retrieve('senior-chef');
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Every method accepts trailing `FetchOptions` — `signal`, `headers`, and
|
|
46
|
+
framework caching directives (`next: { tags }`, `cf: {…}`) pass through to
|
|
47
|
+
`fetch` untouched. Walk full catalogs with the async iterator:
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import { paginate } from '@cavuno/board';
|
|
51
|
+
|
|
52
|
+
// the query `limit` is the page size; toArray's `limit` caps items collected
|
|
53
|
+
for await (const card of paginate(board.jobs.list, { limit: 100 })) {
|
|
54
|
+
urls.push(card.links.public);
|
|
55
|
+
}
|
|
56
|
+
const first500 = await paginate(board.companies.list, { limit: 100 })
|
|
57
|
+
.toArray({ limit: 500 });
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Errors are typed — every non-2xx throws a `BoardApiError` carrying the full
|
|
61
|
+
v1 envelope (`status`, `code`, `details`, `requestId`), with guards like
|
|
62
|
+
`isNotFound`, `isRateLimited`, and `isBoardPasswordRequired`.
|
|
63
|
+
|
|
64
|
+
## Auth model
|
|
65
|
+
|
|
66
|
+
Three tiers, safe for browsers by construction:
|
|
67
|
+
|
|
68
|
+
- **`pk_…` publishable key** — identifies the board; public by design.
|
|
69
|
+
- **Board-user JWT** — candidate/employer sessions via `board.auth.*`.
|
|
70
|
+
Pluggable storage (`memory` in the browser, `nostore` on the server);
|
|
71
|
+
on SSR, keep the session in an httpOnly cookie your app owns and pass
|
|
72
|
+
the token per call. No auto-refresh on 401 — rotation is explicit.
|
|
73
|
+
- **No secret keys** — operator/admin credentials never touch this SDK.
|
|
74
|
+
|
|
75
|
+
## Escape hatch
|
|
76
|
+
|
|
77
|
+
`board.client.fetch<T>(path, init)` sends a typed request through the full
|
|
78
|
+
pipeline (board base path, headers, bearer token, hooks) for endpoints the
|
|
79
|
+
SDK doesn't cover yet.
|
|
80
|
+
|
|
81
|
+
## Docs
|
|
82
|
+
|
|
83
|
+
- API reference & guides: https://cavuno.com/docs/api
|
|
84
|
+
- The OpenAPI document: `GET https://api.cavuno.com/v1/openapi.json`
|
|
85
|
+
- Reference starter (TanStack Start on Cloudflare Workers):
|
|
86
|
+
https://github.com/wollemiahq/cavuno-board-starter
|
|
87
|
+
|
|
88
|
+
MIT © Wollemia
|
package/dist/bin.mjs
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __esm = (fn, res) => function __init() {
|
|
4
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
|
+
};
|
|
6
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
7
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
8
|
+
};
|
|
2
9
|
|
|
3
10
|
// src/skills.ts
|
|
4
11
|
import { readFileSync } from "fs";
|
|
@@ -14,6 +21,11 @@ function loadSkillManifest() {
|
|
|
14
21
|
const manifestPath = resolveFromPackageRoot("skills/manifest.json");
|
|
15
22
|
return JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
16
23
|
}
|
|
24
|
+
var init_skills = __esm({
|
|
25
|
+
"src/skills.ts"() {
|
|
26
|
+
"use strict";
|
|
27
|
+
}
|
|
28
|
+
});
|
|
17
29
|
|
|
18
30
|
// src/setup/run.ts
|
|
19
31
|
import { cpSync, existsSync, mkdirSync, readFileSync as readFileSync2 } from "fs";
|
|
@@ -32,43 +44,64 @@ function runSetup(cwd = process.cwd()) {
|
|
|
32
44
|
const chosen = manifest.skills.filter(
|
|
33
45
|
(skill) => skill.category === "core" || skill.framework === framework
|
|
34
46
|
);
|
|
35
|
-
const
|
|
36
|
-
|
|
47
|
+
const roots = [
|
|
48
|
+
resolve2(cwd, ".claude", "skills"),
|
|
49
|
+
resolve2(cwd, ".agents", "skills")
|
|
50
|
+
];
|
|
51
|
+
const existing = roots.filter((root) => existsSync(root));
|
|
52
|
+
const targetDirs = existing.length > 0 ? existing : [roots[0]];
|
|
37
53
|
const copied = [];
|
|
38
|
-
for (const
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
54
|
+
for (const targetDir of targetDirs) {
|
|
55
|
+
mkdirSync(targetDir, { recursive: true });
|
|
56
|
+
for (const skill of chosen) {
|
|
57
|
+
const sourceDir = dirname2(resolveFromPackageRoot(skill.path));
|
|
58
|
+
const destDir = resolve2(targetDir, skill.name);
|
|
59
|
+
mkdirSync(destDir, { recursive: true });
|
|
60
|
+
cpSync(sourceDir, destDir, { recursive: true });
|
|
61
|
+
if (!copied.includes(skill.name)) copied.push(skill.name);
|
|
62
|
+
}
|
|
44
63
|
}
|
|
45
|
-
return { version: manifest.version, framework,
|
|
64
|
+
return { version: manifest.version, framework, targetDirs, copied };
|
|
46
65
|
}
|
|
66
|
+
var init_run = __esm({
|
|
67
|
+
"src/setup/run.ts"() {
|
|
68
|
+
"use strict";
|
|
69
|
+
init_skills();
|
|
70
|
+
}
|
|
71
|
+
});
|
|
47
72
|
|
|
48
73
|
// src/bin.ts
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
74
|
+
var require_bin = __commonJS({
|
|
75
|
+
"src/bin.ts"() {
|
|
76
|
+
init_run();
|
|
77
|
+
function main() {
|
|
78
|
+
if (process.argv[2] !== "setup") {
|
|
79
|
+
console.error("Usage: cavuno-board setup");
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
const result = runSetup();
|
|
83
|
+
console.log(`
|
|
56
84
|
@cavuno/board setup \u2014 v${result.version}`);
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
85
|
+
console.log(
|
|
86
|
+
`Detected framework: ${result.framework ?? "none (core skills only)"}`
|
|
87
|
+
);
|
|
88
|
+
console.log(
|
|
89
|
+
`Copied ${result.copied.length} skills \u2192 ${result.targetDirs.join(", ")}`
|
|
90
|
+
);
|
|
91
|
+
for (const name of result.copied) console.log(` - ${name}`);
|
|
92
|
+
console.log("\nNext steps:");
|
|
93
|
+
console.log(" 1. Set your environment variables:");
|
|
94
|
+
console.log(" PUBLIC_CAVUNO_API_URL=https://api.cavuno.com");
|
|
95
|
+
console.log(
|
|
96
|
+
" PUBLIC_CAVUNO_BOARD=pk_... # your board publishable key"
|
|
97
|
+
);
|
|
98
|
+
console.log(' 2. Ask your coding agent: "set up my Cavuno board".');
|
|
99
|
+
console.log(" It reads the cavuno-board-setup skill first.");
|
|
100
|
+
console.log(
|
|
101
|
+
"\n Re-run `npx @cavuno/board setup` after upgrading to refresh skills."
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
main();
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
export default require_bin();
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { J as JobSort, E as EmploymentType, R as RemoteOption, S as Seniority } from './jobs-DK5mPBgq.mjs';
|
|
2
|
+
|
|
3
|
+
declare const REMOTE_OPTIONS: readonly RemoteOption[];
|
|
4
|
+
/**
|
|
5
|
+
* The employment types the listing FILTER offers (the hosted filter UI's
|
|
6
|
+
* set) — `volunteer`/`other` exist on the wire but are not filter options.
|
|
7
|
+
*/
|
|
8
|
+
declare const EMPLOYMENT_TYPES: readonly EmploymentType[];
|
|
9
|
+
/** All 8 platform seniority levels — the hosted filter renders the full set as a multi-select. */
|
|
10
|
+
declare const SENIORITIES: readonly Seniority[];
|
|
11
|
+
declare const JOB_SORTS: readonly JobSort[];
|
|
12
|
+
declare const DEFAULT_SORT: JobSort;
|
|
13
|
+
/**
|
|
14
|
+
* Seniority display labels in the board language (lexicon-backed, en+de).
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* seniorityLabels('de').executive; // "Führungskraft"
|
|
18
|
+
*/
|
|
19
|
+
declare function seniorityLabels(locale: string): Record<Seniority, string>;
|
|
20
|
+
/**
|
|
21
|
+
* Sort-dropdown labels. The hosted board has no centralized sort vocabulary
|
|
22
|
+
* yet, so every locale receives these English defaults today. The locale
|
|
23
|
+
* parameter is CONTRACT, not cosmetics: ADR-0057 makes the board language a
|
|
24
|
+
* required leading argument on every label-producing helper so call sites
|
|
25
|
+
* are locale-correct by construction — when a sort vocabulary lands, no
|
|
26
|
+
* consumer changes shape (adding the parameter later would be a breaking
|
|
27
|
+
* change to every call site).
|
|
28
|
+
*/
|
|
29
|
+
declare function sortLabels(_locale: string): Record<JobSort, string>;
|
|
30
|
+
interface ListingFilters {
|
|
31
|
+
q?: string;
|
|
32
|
+
remoteOption?: RemoteOption;
|
|
33
|
+
employmentType?: EmploymentType;
|
|
34
|
+
/** Multi-select (parity with the hosted board's seniority checkboxes). */
|
|
35
|
+
seniority?: Seniority[];
|
|
36
|
+
/** Result ordering (ADR-0048); absent ⇒ `relevance`. */
|
|
37
|
+
sort?: JobSort;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Normalise raw seniority input — an array (repeated params) or a
|
|
41
|
+
* comma-string (hand-typed URL) — to valid levels, with the hosted
|
|
42
|
+
* semantics: trim, lowercase, drop unknowns, dedupe preserving order.
|
|
43
|
+
*/
|
|
44
|
+
declare function parseSeniority(raw: unknown): Seniority[] | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* Validate raw URL search params into the supported listing filters,
|
|
47
|
+
* dropping unknown values (never throwing — listing URLs are public input).
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* parseListingFilters({ seniority: 'senior,lead', sort: 'newest' });
|
|
51
|
+
* // { seniority: ['senior', 'lead'], sort: 'newest' }
|
|
52
|
+
*/
|
|
53
|
+
declare function parseListingFilters(search: Record<string, unknown>): ListingFilters;
|
|
54
|
+
|
|
55
|
+
export { DEFAULT_SORT, EMPLOYMENT_TYPES, JOB_SORTS, type ListingFilters, REMOTE_OPTIONS, SENIORITIES, parseListingFilters, parseSeniority, seniorityLabels, sortLabels };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { J as JobSort, E as EmploymentType, R as RemoteOption, S as Seniority } from './jobs-DK5mPBgq.js';
|
|
2
|
+
|
|
3
|
+
declare const REMOTE_OPTIONS: readonly RemoteOption[];
|
|
4
|
+
/**
|
|
5
|
+
* The employment types the listing FILTER offers (the hosted filter UI's
|
|
6
|
+
* set) — `volunteer`/`other` exist on the wire but are not filter options.
|
|
7
|
+
*/
|
|
8
|
+
declare const EMPLOYMENT_TYPES: readonly EmploymentType[];
|
|
9
|
+
/** All 8 platform seniority levels — the hosted filter renders the full set as a multi-select. */
|
|
10
|
+
declare const SENIORITIES: readonly Seniority[];
|
|
11
|
+
declare const JOB_SORTS: readonly JobSort[];
|
|
12
|
+
declare const DEFAULT_SORT: JobSort;
|
|
13
|
+
/**
|
|
14
|
+
* Seniority display labels in the board language (lexicon-backed, en+de).
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* seniorityLabels('de').executive; // "Führungskraft"
|
|
18
|
+
*/
|
|
19
|
+
declare function seniorityLabels(locale: string): Record<Seniority, string>;
|
|
20
|
+
/**
|
|
21
|
+
* Sort-dropdown labels. The hosted board has no centralized sort vocabulary
|
|
22
|
+
* yet, so every locale receives these English defaults today. The locale
|
|
23
|
+
* parameter is CONTRACT, not cosmetics: ADR-0057 makes the board language a
|
|
24
|
+
* required leading argument on every label-producing helper so call sites
|
|
25
|
+
* are locale-correct by construction — when a sort vocabulary lands, no
|
|
26
|
+
* consumer changes shape (adding the parameter later would be a breaking
|
|
27
|
+
* change to every call site).
|
|
28
|
+
*/
|
|
29
|
+
declare function sortLabels(_locale: string): Record<JobSort, string>;
|
|
30
|
+
interface ListingFilters {
|
|
31
|
+
q?: string;
|
|
32
|
+
remoteOption?: RemoteOption;
|
|
33
|
+
employmentType?: EmploymentType;
|
|
34
|
+
/** Multi-select (parity with the hosted board's seniority checkboxes). */
|
|
35
|
+
seniority?: Seniority[];
|
|
36
|
+
/** Result ordering (ADR-0048); absent ⇒ `relevance`. */
|
|
37
|
+
sort?: JobSort;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Normalise raw seniority input — an array (repeated params) or a
|
|
41
|
+
* comma-string (hand-typed URL) — to valid levels, with the hosted
|
|
42
|
+
* semantics: trim, lowercase, drop unknowns, dedupe preserving order.
|
|
43
|
+
*/
|
|
44
|
+
declare function parseSeniority(raw: unknown): Seniority[] | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* Validate raw URL search params into the supported listing filters,
|
|
47
|
+
* dropping unknown values (never throwing — listing URLs are public input).
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* parseListingFilters({ seniority: 'senior,lead', sort: 'newest' });
|
|
51
|
+
* // { seniority: ['senior', 'lead'], sort: 'newest' }
|
|
52
|
+
*/
|
|
53
|
+
declare function parseListingFilters(search: Record<string, unknown>): ListingFilters;
|
|
54
|
+
|
|
55
|
+
export { DEFAULT_SORT, EMPLOYMENT_TYPES, JOB_SORTS, type ListingFilters, REMOTE_OPTIONS, SENIORITIES, parseListingFilters, parseSeniority, seniorityLabels, sortLabels };
|
package/dist/filters.js
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/filters/index.ts
|
|
21
|
+
var filters_exports = {};
|
|
22
|
+
__export(filters_exports, {
|
|
23
|
+
DEFAULT_SORT: () => DEFAULT_SORT,
|
|
24
|
+
EMPLOYMENT_TYPES: () => EMPLOYMENT_TYPES,
|
|
25
|
+
JOB_SORTS: () => JOB_SORTS,
|
|
26
|
+
REMOTE_OPTIONS: () => REMOTE_OPTIONS,
|
|
27
|
+
SENIORITIES: () => SENIORITIES,
|
|
28
|
+
parseListingFilters: () => parseListingFilters,
|
|
29
|
+
parseSeniority: () => parseSeniority,
|
|
30
|
+
seniorityLabels: () => seniorityLabels,
|
|
31
|
+
sortLabels: () => sortLabels
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(filters_exports);
|
|
34
|
+
|
|
35
|
+
// src/format/salary-lexicon.ts
|
|
36
|
+
var EN = {
|
|
37
|
+
timeframe: {
|
|
38
|
+
per_year: "Yearly",
|
|
39
|
+
per_month: "Monthly",
|
|
40
|
+
per_week: "Weekly",
|
|
41
|
+
per_day: "Daily",
|
|
42
|
+
per_hour: "Hourly"
|
|
43
|
+
},
|
|
44
|
+
rangePrefix: { from: "From", upTo: "Up to" },
|
|
45
|
+
seniority: {
|
|
46
|
+
entry_level: "Entry level",
|
|
47
|
+
associate: "Associate",
|
|
48
|
+
mid_level: "Mid-level",
|
|
49
|
+
senior: "Senior",
|
|
50
|
+
lead: "Lead",
|
|
51
|
+
principal: "Principal",
|
|
52
|
+
director: "Director",
|
|
53
|
+
executive: "Executive"
|
|
54
|
+
},
|
|
55
|
+
frames: {
|
|
56
|
+
entitySalariesTitle: ({ entity, range }) => `${entity} Salaries (${range}/yr)`,
|
|
57
|
+
salariesInPlaceTitle: ({ place, range }) => `Salaries in ${place} (${range}/yr)`,
|
|
58
|
+
salariesInPlaceTitleNoRange: ({ place }) => `Salaries in ${place}`,
|
|
59
|
+
entitySalariesInPlaceTitle: ({ entity, place, range }) => `${entity} Salaries in ${place} (${range}/yr)`,
|
|
60
|
+
companySalariesTitle: ({ company, range }) => `${company} Salaries${range ? ` (${range}/yr)` : ""}`,
|
|
61
|
+
companyCategorySalariesTitle: ({ company, category, range }) => `${company} ${category} Salaries${range ? ` (${range}/yr)` : ""}`,
|
|
62
|
+
professionalsEarn: ({ entity, range, count }) => `${entity} professionals earn ${range}/yr on average across ${count} job postings.`,
|
|
63
|
+
rolesPay: ({ entity, range, count }) => `${entity} roles pay ${range}/yr on average across ${count} job postings.`,
|
|
64
|
+
professionalsInLocationEarn: ({ entity, place, range, count }) => `${entity} professionals in ${place} earn ${range}/yr on average across ${count} job postings.`,
|
|
65
|
+
seniorityRange: ({ lowLabel, lowAmount, highLabel, highAmount }) => `${lowLabel} roles start at ${lowAmount}, ${highLabel} roles reach ${highAmount}.`,
|
|
66
|
+
seniorityRangeWhile: ({ lowLabel, lowAmount, highLabel, highAmount }) => `${lowLabel} salaries start at ${lowAmount}, while ${highLabel} roles reach ${highAmount}.`,
|
|
67
|
+
compareAcrossTopPaying: ({ count }) => `Compare across ${count} top-paying ${count === 1 ? "company" : "companies"}.`,
|
|
68
|
+
compareAcrossTop: ({ count }) => `Compare across ${count} top ${count === 1 ? "company" : "companies"}.`,
|
|
69
|
+
boardWideAverage: ({ entity, range }) => `Board-wide ${entity} average: ${range}/yr.`,
|
|
70
|
+
averageSalaryInCity: ({ place, range, count }) => `Average salary in ${place} is ${range}/yr across ${count} jobs. Browse salary data by title and skill.`,
|
|
71
|
+
browseLocationsInRegion: ({ count, place }) => `Browse salary data across ${count} locations in ${place}. Compare salaries by title, skill, and company.`,
|
|
72
|
+
companyPays: ({ company, range, count }) => `${company} pays ${range}/yr on average across ${count} job postings.`,
|
|
73
|
+
breakdownAcrossCategories: ({ count }) => `Breakdown across ${count} job ${count === 1 ? "category" : "categories"}.`,
|
|
74
|
+
compareWithSimilar: ({ count }) => `Compare with ${count} similar ${count === 1 ? "company" : "companies"}.`,
|
|
75
|
+
salaryInfoFor: ({ company, board }) => `Salary information for ${company} on ${board}.`,
|
|
76
|
+
categoryRolesAtCompanyPay: ({ category, company, range, count }) => `${category} roles at ${company} pay ${range}/yr based on ${count} job postings.`,
|
|
77
|
+
categorySalaryDataFor: ({ category, company, board }) => `${category} salary data for ${company} on ${board}.`
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
var DE = {
|
|
81
|
+
timeframe: {
|
|
82
|
+
per_year: "pro Jahr",
|
|
83
|
+
per_month: "pro Monat",
|
|
84
|
+
per_week: "pro Woche",
|
|
85
|
+
per_day: "pro Tag",
|
|
86
|
+
per_hour: "pro Stunde"
|
|
87
|
+
},
|
|
88
|
+
rangePrefix: { from: "ab", upTo: "bis zu" },
|
|
89
|
+
seniority: {
|
|
90
|
+
entry_level: "Entry-Level",
|
|
91
|
+
associate: "Associate",
|
|
92
|
+
mid_level: "Mid-Level",
|
|
93
|
+
senior: "Senior",
|
|
94
|
+
lead: "Lead",
|
|
95
|
+
principal: "Principal",
|
|
96
|
+
director: "Director",
|
|
97
|
+
executive: "F\xFChrungskraft"
|
|
98
|
+
},
|
|
99
|
+
frames: {
|
|
100
|
+
entitySalariesTitle: ({ entity, range }) => `${entity} Geh\xE4lter (${range}/Jahr)`,
|
|
101
|
+
salariesInPlaceTitle: ({ place, range }) => `Geh\xE4lter in ${place} (${range}/Jahr)`,
|
|
102
|
+
salariesInPlaceTitleNoRange: ({ place }) => `Geh\xE4lter in ${place}`,
|
|
103
|
+
entitySalariesInPlaceTitle: ({ entity, place, range }) => `${entity} Geh\xE4lter in ${place} (${range}/Jahr)`,
|
|
104
|
+
companySalariesTitle: ({ company, range }) => `${company} Geh\xE4lter${range ? ` (${range}/Jahr)` : ""}`,
|
|
105
|
+
companyCategorySalariesTitle: ({ company, category, range }) => `${company} ${category} Geh\xE4lter${range ? ` (${range}/Jahr)` : ""}`,
|
|
106
|
+
professionalsEarn: ({ entity, range, count }) => `${entity}-Fachkr\xE4fte verdienen durchschnittlich ${range}/Jahr, basierend auf ${count} Stellenanzeigen.`,
|
|
107
|
+
rolesPay: ({ entity, range, count }) => `${entity}-Positionen werden mit durchschnittlich ${range}/Jahr verg\xFCtet, basierend auf ${count} Stellenanzeigen.`,
|
|
108
|
+
professionalsInLocationEarn: ({ entity, place, range, count }) => `${entity}-Fachkr\xE4fte in ${place} verdienen durchschnittlich ${range}/Jahr, basierend auf ${count} Stellenanzeigen.`,
|
|
109
|
+
seniorityRange: ({ lowLabel, lowAmount, highLabel, highAmount }) => `${lowLabel}-Positionen beginnen bei ${lowAmount}, ${highLabel}-Positionen erreichen ${highAmount}.`,
|
|
110
|
+
seniorityRangeWhile: ({ lowLabel, lowAmount, highLabel, highAmount }) => `${lowLabel}-Geh\xE4lter beginnen bei ${lowAmount}, w\xE4hrend ${highLabel}-Positionen ${highAmount} erreichen.`,
|
|
111
|
+
compareAcrossTopPaying: ({ count }) => `Vergleichen Sie ${count} ${count === 1 ? "bestzahlendes" : "bestzahlende"} Unternehmen.`,
|
|
112
|
+
compareAcrossTop: ({ count }) => `Vergleichen Sie ${count} Top-Unternehmen.`,
|
|
113
|
+
boardWideAverage: ({ entity, range }) => `Gesamtdurchschnitt ${entity}: ${range}/Jahr.`,
|
|
114
|
+
averageSalaryInCity: ({ place, range, count }) => `Das Durchschnittsgehalt in ${place} betr\xE4gt ${range}/Jahr, basierend auf ${count} Stellen. Gehaltsdaten nach Titel und F\xE4higkeiten durchsuchen.`,
|
|
115
|
+
browseLocationsInRegion: ({ count, place }) => `Gehaltsdaten f\xFCr ${count} Standorte in ${place} durchsuchen. Geh\xE4lter nach Titel, F\xE4higkeiten und Unternehmen vergleichen.`,
|
|
116
|
+
companyPays: ({ company, range, count }) => `${company} zahlt durchschnittlich ${range}/Jahr, basierend auf ${count} Stellenanzeigen.`,
|
|
117
|
+
breakdownAcrossCategories: ({ count }) => `Aufschl\xFCsselung \xFCber ${count} Job-${count === 1 ? "Kategorie" : "Kategorien"}.`,
|
|
118
|
+
compareWithSimilar: ({ count }) => `Vergleichen Sie mit ${count} \xE4hnlichen Unternehmen.`,
|
|
119
|
+
salaryInfoFor: ({ company, board }) => `Gehaltsinformationen f\xFCr ${company} auf ${board}.`,
|
|
120
|
+
categoryRolesAtCompanyPay: ({ category, company, range, count }) => `${category}-Positionen bei ${company} werden mit ${range}/Jahr verg\xFCtet, basierend auf ${count} Stellenanzeigen.`,
|
|
121
|
+
categorySalaryDataFor: ({ category, company, board }) => `${category}-Gehaltsdaten f\xFCr ${company} auf ${board}.`
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
var LEXICONS = { en: EN, de: DE };
|
|
125
|
+
function getSalaryLexicon(locale) {
|
|
126
|
+
return locale && LEXICONS[locale] || EN;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// src/filters/index.ts
|
|
130
|
+
var REMOTE_OPTIONS = [
|
|
131
|
+
"on_site",
|
|
132
|
+
"hybrid",
|
|
133
|
+
"remote"
|
|
134
|
+
];
|
|
135
|
+
var EMPLOYMENT_TYPES = [
|
|
136
|
+
"full_time",
|
|
137
|
+
"part_time",
|
|
138
|
+
"contract",
|
|
139
|
+
"internship",
|
|
140
|
+
"temporary"
|
|
141
|
+
];
|
|
142
|
+
var SENIORITIES = [
|
|
143
|
+
"entry_level",
|
|
144
|
+
"associate",
|
|
145
|
+
"mid_level",
|
|
146
|
+
"senior",
|
|
147
|
+
"lead",
|
|
148
|
+
"principal",
|
|
149
|
+
"director",
|
|
150
|
+
"executive"
|
|
151
|
+
];
|
|
152
|
+
var JOB_SORTS = [
|
|
153
|
+
"relevance",
|
|
154
|
+
"newest",
|
|
155
|
+
"salary_high"
|
|
156
|
+
];
|
|
157
|
+
var DEFAULT_SORT = "relevance";
|
|
158
|
+
function seniorityLabels(locale) {
|
|
159
|
+
return getSalaryLexicon(locale).seniority;
|
|
160
|
+
}
|
|
161
|
+
function sortLabels(_locale) {
|
|
162
|
+
return {
|
|
163
|
+
relevance: "AI-ranked",
|
|
164
|
+
newest: "Most recent",
|
|
165
|
+
salary_high: "Salary: high to low"
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
var SENIORITY_LOOKUP = new Set(SENIORITIES);
|
|
169
|
+
function parseSeniority(raw) {
|
|
170
|
+
const values = Array.isArray(raw) ? raw : typeof raw === "string" && raw ? raw.split(",") : [];
|
|
171
|
+
const result = [];
|
|
172
|
+
const seen = /* @__PURE__ */ new Set();
|
|
173
|
+
for (const value of values) {
|
|
174
|
+
if (typeof value !== "string") continue;
|
|
175
|
+
const normalized = value.trim().toLowerCase();
|
|
176
|
+
if (!normalized || seen.has(normalized)) continue;
|
|
177
|
+
if (!SENIORITY_LOOKUP.has(normalized)) continue;
|
|
178
|
+
seen.add(normalized);
|
|
179
|
+
result.push(normalized);
|
|
180
|
+
}
|
|
181
|
+
return result.length > 0 ? result : void 0;
|
|
182
|
+
}
|
|
183
|
+
function parseListingFilters(search) {
|
|
184
|
+
return {
|
|
185
|
+
q: typeof search.q === "string" && search.q ? search.q : void 0,
|
|
186
|
+
remoteOption: REMOTE_OPTIONS.includes(search.remoteOption) ? search.remoteOption : void 0,
|
|
187
|
+
employmentType: EMPLOYMENT_TYPES.includes(
|
|
188
|
+
search.employmentType
|
|
189
|
+
) ? search.employmentType : void 0,
|
|
190
|
+
seniority: parseSeniority(search.seniority),
|
|
191
|
+
sort: JOB_SORTS.includes(search.sort) ? search.sort : void 0
|
|
192
|
+
};
|
|
193
|
+
}
|