@cavuno/board 1.0.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 +538 -0
- package/dist/index.d.ts +538 -0
- package/dist/index.js +644 -0
- package/dist/index.mjs +621 -0
- package/package.json +49 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,644 @@
|
|
|
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/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
ACCESS_TOKEN_KEY: () => ACCESS_TOKEN_KEY,
|
|
24
|
+
BoardApiError: () => BoardApiError,
|
|
25
|
+
BoardClient: () => BoardClient,
|
|
26
|
+
REFRESH_TOKEN_KEY: () => REFRESH_TOKEN_KEY,
|
|
27
|
+
SDK_VERSION: () => SDK_VERSION,
|
|
28
|
+
createBoardClient: () => createBoardClient,
|
|
29
|
+
isBoardApiError: () => isBoardApiError,
|
|
30
|
+
isConflict: () => isConflict,
|
|
31
|
+
isForbidden: () => isForbidden,
|
|
32
|
+
isNotFound: () => isNotFound,
|
|
33
|
+
isRateLimited: () => isRateLimited,
|
|
34
|
+
isUnauthorized: () => isUnauthorized,
|
|
35
|
+
isValidationError: () => isValidationError
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(index_exports);
|
|
38
|
+
|
|
39
|
+
// src/errors.ts
|
|
40
|
+
var BoardApiError = class extends Error {
|
|
41
|
+
status;
|
|
42
|
+
/** `<domain>_<snake_reason>` code from the v1 error envelope. */
|
|
43
|
+
code;
|
|
44
|
+
/** Structured, per-code details — shape varies by `code`. */
|
|
45
|
+
details;
|
|
46
|
+
requestId;
|
|
47
|
+
/** The parsed response body, untouched. */
|
|
48
|
+
raw;
|
|
49
|
+
constructor(input) {
|
|
50
|
+
super(input.message);
|
|
51
|
+
this.name = "BoardApiError";
|
|
52
|
+
this.status = input.status;
|
|
53
|
+
this.code = input.code;
|
|
54
|
+
this.details = input.details;
|
|
55
|
+
this.requestId = input.requestId;
|
|
56
|
+
this.raw = input.raw;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
function isBoardApiError(e) {
|
|
60
|
+
return e instanceof BoardApiError;
|
|
61
|
+
}
|
|
62
|
+
function isNotFound(e) {
|
|
63
|
+
return isBoardApiError(e) && e.status === 404;
|
|
64
|
+
}
|
|
65
|
+
function isUnauthorized(e) {
|
|
66
|
+
return isBoardApiError(e) && e.status === 401;
|
|
67
|
+
}
|
|
68
|
+
function isForbidden(e) {
|
|
69
|
+
return isBoardApiError(e) && e.status === 403;
|
|
70
|
+
}
|
|
71
|
+
function isValidationError(e) {
|
|
72
|
+
return isBoardApiError(e) && e.status === 400 && e.code === "validation_bad_request";
|
|
73
|
+
}
|
|
74
|
+
function isRateLimited(e) {
|
|
75
|
+
return isBoardApiError(e) && e.status === 429;
|
|
76
|
+
}
|
|
77
|
+
function isConflict(e) {
|
|
78
|
+
return isBoardApiError(e) && e.status === 409;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// src/query.ts
|
|
82
|
+
function toSearchParams(query) {
|
|
83
|
+
const params = new URLSearchParams();
|
|
84
|
+
if (!query) return params;
|
|
85
|
+
for (const [key, value] of Object.entries(query)) {
|
|
86
|
+
if (value === void 0 || value === null) continue;
|
|
87
|
+
if (Array.isArray(value)) {
|
|
88
|
+
for (const item of value) {
|
|
89
|
+
if (item === void 0 || item === null) continue;
|
|
90
|
+
params.append(key, String(item));
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
params.append(key, String(value));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return params;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// src/storage.ts
|
|
100
|
+
var ACCESS_TOKEN_KEY = "cavuno_board_access_token";
|
|
101
|
+
var REFRESH_TOKEN_KEY = "cavuno_board_refresh_token";
|
|
102
|
+
function isBrowser() {
|
|
103
|
+
return typeof globalThis.document !== "undefined";
|
|
104
|
+
}
|
|
105
|
+
function memoryStorage() {
|
|
106
|
+
const backing = /* @__PURE__ */ new Map();
|
|
107
|
+
return {
|
|
108
|
+
getItem: (key) => backing.get(key) ?? null,
|
|
109
|
+
setItem: (key, value) => {
|
|
110
|
+
backing.set(key, value);
|
|
111
|
+
},
|
|
112
|
+
removeItem: (key) => {
|
|
113
|
+
backing.delete(key);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
var NOSTORE = {
|
|
118
|
+
getItem: () => null,
|
|
119
|
+
setItem: () => {
|
|
120
|
+
},
|
|
121
|
+
removeItem: () => {
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
function resolveStorage(mode) {
|
|
125
|
+
const resolved = mode ?? (isBrowser() ? "memory" : "nostore");
|
|
126
|
+
if (typeof resolved === "object") return resolved;
|
|
127
|
+
switch (resolved) {
|
|
128
|
+
case "memory":
|
|
129
|
+
return memoryStorage();
|
|
130
|
+
case "nostore":
|
|
131
|
+
return NOSTORE;
|
|
132
|
+
default:
|
|
133
|
+
throw new Error(`Unknown storage mode '${String(resolved)}'`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
async function writeSession(storage, session) {
|
|
137
|
+
await storage.setItem(ACCESS_TOKEN_KEY, session.accessToken);
|
|
138
|
+
await storage.setItem(REFRESH_TOKEN_KEY, session.refreshToken);
|
|
139
|
+
}
|
|
140
|
+
async function clearSession(storage) {
|
|
141
|
+
await storage.removeItem(ACCESS_TOKEN_KEY);
|
|
142
|
+
await storage.removeItem(REFRESH_TOKEN_KEY);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// src/version.ts
|
|
146
|
+
var SDK_VERSION = "1.0.0";
|
|
147
|
+
|
|
148
|
+
// src/client.ts
|
|
149
|
+
function isRawBody(body) {
|
|
150
|
+
return typeof body === "string" || body instanceof URLSearchParams || typeof FormData !== "undefined" && body instanceof FormData || typeof Blob !== "undefined" && body instanceof Blob || body instanceof ArrayBuffer || typeof ReadableStream !== "undefined" && body instanceof ReadableStream;
|
|
151
|
+
}
|
|
152
|
+
var BoardClient = class {
|
|
153
|
+
storage;
|
|
154
|
+
basePath;
|
|
155
|
+
options;
|
|
156
|
+
constructor(options) {
|
|
157
|
+
this.options = options;
|
|
158
|
+
this.storage = options.storage;
|
|
159
|
+
const baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
160
|
+
this.basePath = `${baseUrl}/v1/boards/${encodeURIComponent(options.board)}`;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* The full request pipeline. Public and first-class: custom endpoints
|
|
164
|
+
* work without an SDK release, and still get the board-identifier
|
|
165
|
+
* base path, default headers, bearer token, and both hooks.
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* const stats = await board.client.fetch<MyShape>('/custom/stats');
|
|
169
|
+
*/
|
|
170
|
+
async fetch(path, init = {}) {
|
|
171
|
+
const { body, query, headers: callHeaders, ...passthrough } = init;
|
|
172
|
+
const method = (init.method ?? "GET").toUpperCase();
|
|
173
|
+
let url = `${this.basePath}${path}`;
|
|
174
|
+
const params = toSearchParams(query);
|
|
175
|
+
const search = params.toString();
|
|
176
|
+
if (search) url += `?${search}`;
|
|
177
|
+
const jsonBody = body !== void 0 && !isRawBody(body);
|
|
178
|
+
const serializedBody = body === void 0 ? void 0 : jsonBody ? JSON.stringify(body) : body;
|
|
179
|
+
const headers = new Headers({ accept: "application/json" });
|
|
180
|
+
if (jsonBody) headers.set("content-type", "application/json");
|
|
181
|
+
for (const [key, value] of Object.entries(
|
|
182
|
+
this.options.globalHeaders ?? {}
|
|
183
|
+
)) {
|
|
184
|
+
headers.set(key, value);
|
|
185
|
+
}
|
|
186
|
+
const token = await this.storage.getItem(ACCESS_TOKEN_KEY);
|
|
187
|
+
if (token !== null) headers.set("authorization", `Bearer ${token}`);
|
|
188
|
+
if (callHeaders) {
|
|
189
|
+
new Headers(callHeaders).forEach((value, key) => {
|
|
190
|
+
headers.set(key, value);
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
if ((method !== "GET" || headers.has("authorization")) && !headers.has("x-cavuno-sdk")) {
|
|
194
|
+
headers.set("x-cavuno-sdk", `board@${SDK_VERSION}`);
|
|
195
|
+
}
|
|
196
|
+
let req = {
|
|
197
|
+
url,
|
|
198
|
+
init: { ...passthrough, method, headers, body: serializedBody }
|
|
199
|
+
};
|
|
200
|
+
if (this.options.onRequest) req = await this.options.onRequest(req);
|
|
201
|
+
this.options.logger?.debug(`${method} ${req.url}`);
|
|
202
|
+
const res = await globalThis.fetch(req.url, req.init);
|
|
203
|
+
this.options.logger?.debug(`${res.status} ${method} ${req.url}`);
|
|
204
|
+
if (this.options.onResponse) await this.options.onResponse(res.clone(), req);
|
|
205
|
+
if (res.status === 204) return void 0;
|
|
206
|
+
if (res.ok) return await res.json();
|
|
207
|
+
let parsed;
|
|
208
|
+
try {
|
|
209
|
+
parsed = await res.json();
|
|
210
|
+
} catch {
|
|
211
|
+
parsed = void 0;
|
|
212
|
+
}
|
|
213
|
+
const envelope = parsed?.error;
|
|
214
|
+
const error = envelope && typeof envelope.code === "string" && typeof envelope.message === "string" ? new BoardApiError({
|
|
215
|
+
status: res.status,
|
|
216
|
+
code: envelope.code,
|
|
217
|
+
message: envelope.message,
|
|
218
|
+
details: envelope.details,
|
|
219
|
+
requestId: envelope.requestId,
|
|
220
|
+
raw: parsed
|
|
221
|
+
}) : new BoardApiError({
|
|
222
|
+
status: res.status,
|
|
223
|
+
code: "unknown_error",
|
|
224
|
+
message: res.statusText || "Request failed",
|
|
225
|
+
raw: parsed
|
|
226
|
+
});
|
|
227
|
+
this.options.logger?.error(
|
|
228
|
+
`${error.status} ${error.code} ${method} ${req.url}`
|
|
229
|
+
);
|
|
230
|
+
throw error;
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
// src/namespaces/auth.ts
|
|
235
|
+
function authNamespace(client) {
|
|
236
|
+
async function persist(session) {
|
|
237
|
+
await writeSession(client.storage, session);
|
|
238
|
+
return session;
|
|
239
|
+
}
|
|
240
|
+
async function resolveRefreshToken(body) {
|
|
241
|
+
if (body?.refreshToken) return body.refreshToken;
|
|
242
|
+
const stored = await client.storage.getItem(REFRESH_TOKEN_KEY);
|
|
243
|
+
if (stored === null) {
|
|
244
|
+
throw new Error(
|
|
245
|
+
"No refresh token available \u2014 pass { refreshToken } or configure auth.storage"
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
return stored;
|
|
249
|
+
}
|
|
250
|
+
return {
|
|
251
|
+
/**
|
|
252
|
+
* Register a board user (emailpass). Persists the returned token
|
|
253
|
+
* pair to storage and returns the session.
|
|
254
|
+
*
|
|
255
|
+
* @example
|
|
256
|
+
* await board.auth.register({
|
|
257
|
+
* role: 'candidate',
|
|
258
|
+
* method: 'emailpass',
|
|
259
|
+
* email: 'a@b.com',
|
|
260
|
+
* password: 'hunter22',
|
|
261
|
+
* displayName: 'Ada',
|
|
262
|
+
* });
|
|
263
|
+
*/
|
|
264
|
+
async register(body, options) {
|
|
265
|
+
const session = await client.fetch("/auth/register", {
|
|
266
|
+
...options,
|
|
267
|
+
method: "POST",
|
|
268
|
+
body
|
|
269
|
+
});
|
|
270
|
+
return persist(session);
|
|
271
|
+
},
|
|
272
|
+
/**
|
|
273
|
+
* Log in with email + password. Persists the returned token pair to
|
|
274
|
+
* storage and returns the session. The SDK never navigates — the
|
|
275
|
+
* host app owns any redirect/verification UX.
|
|
276
|
+
*
|
|
277
|
+
* @example
|
|
278
|
+
* const { boardUser } = await board.auth.login({
|
|
279
|
+
* email: 'a@b.com',
|
|
280
|
+
* password: 'hunter22',
|
|
281
|
+
* });
|
|
282
|
+
*/
|
|
283
|
+
async login(body, options) {
|
|
284
|
+
const session = await client.fetch("/auth/login", {
|
|
285
|
+
...options,
|
|
286
|
+
method: "POST",
|
|
287
|
+
body
|
|
288
|
+
});
|
|
289
|
+
return persist(session);
|
|
290
|
+
},
|
|
291
|
+
/**
|
|
292
|
+
* Rotate the refresh token for a new bearer pair. Refresh tokens
|
|
293
|
+
* are single-use; the rotated pair is persisted to storage. There
|
|
294
|
+
* is no automatic refresh on 401 — call this explicitly.
|
|
295
|
+
*
|
|
296
|
+
* @example
|
|
297
|
+
* await board.auth.refresh(); // uses the stored refresh token
|
|
298
|
+
*/
|
|
299
|
+
async refresh(body, options) {
|
|
300
|
+
const refreshToken = await resolveRefreshToken(body);
|
|
301
|
+
const session = await client.fetch("/auth/refresh", {
|
|
302
|
+
...options,
|
|
303
|
+
method: "POST",
|
|
304
|
+
body: { refreshToken }
|
|
305
|
+
});
|
|
306
|
+
return persist(session);
|
|
307
|
+
},
|
|
308
|
+
/**
|
|
309
|
+
* Revoke the refresh token and clear stored tokens. Idempotent
|
|
310
|
+
* server-side (204 even for unknown tokens). If the request itself
|
|
311
|
+
* fails (network error, 5xx), stored tokens are deliberately kept —
|
|
312
|
+
* clearing them would leave a live refresh token server-side while
|
|
313
|
+
* the client believes it's logged out. Catch and retry, or clear
|
|
314
|
+
* storage yourself to force a local logout.
|
|
315
|
+
*
|
|
316
|
+
* @example
|
|
317
|
+
* await board.auth.logout();
|
|
318
|
+
*/
|
|
319
|
+
async logout(body, options) {
|
|
320
|
+
const refreshToken = await resolveRefreshToken(body);
|
|
321
|
+
await client.fetch("/auth/logout", {
|
|
322
|
+
...options,
|
|
323
|
+
method: "POST",
|
|
324
|
+
body: { refreshToken }
|
|
325
|
+
});
|
|
326
|
+
await clearSession(client.storage);
|
|
327
|
+
},
|
|
328
|
+
/**
|
|
329
|
+
* Confirm an email-verification token (sent by register).
|
|
330
|
+
*
|
|
331
|
+
* @example
|
|
332
|
+
* await board.auth.verifyEmail({ token });
|
|
333
|
+
*/
|
|
334
|
+
verifyEmail(body, options) {
|
|
335
|
+
return client.fetch("/auth/verify-email", {
|
|
336
|
+
...options,
|
|
337
|
+
method: "POST",
|
|
338
|
+
body
|
|
339
|
+
});
|
|
340
|
+
},
|
|
341
|
+
/**
|
|
342
|
+
* Request a password-reset email. Always resolves (204) whether or
|
|
343
|
+
* not the email exists — no account oracle.
|
|
344
|
+
*
|
|
345
|
+
* @example
|
|
346
|
+
* await board.auth.forgotPassword({ email: 'a@b.com' });
|
|
347
|
+
*/
|
|
348
|
+
forgotPassword(body, options) {
|
|
349
|
+
return client.fetch("/auth/forgot-password", {
|
|
350
|
+
...options,
|
|
351
|
+
method: "POST",
|
|
352
|
+
body
|
|
353
|
+
});
|
|
354
|
+
},
|
|
355
|
+
/**
|
|
356
|
+
* Set a new password with a reset token. The token is single-use;
|
|
357
|
+
* all existing sessions are invalidated server-side.
|
|
358
|
+
*
|
|
359
|
+
* @example
|
|
360
|
+
* await board.auth.resetPassword({ token, password: 'newpass99' });
|
|
361
|
+
*/
|
|
362
|
+
resetPassword(body, options) {
|
|
363
|
+
return client.fetch("/auth/reset-password", {
|
|
364
|
+
...options,
|
|
365
|
+
method: "POST",
|
|
366
|
+
body
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// src/namespaces/blog.ts
|
|
373
|
+
function blogNamespace(client) {
|
|
374
|
+
return {
|
|
375
|
+
posts: {
|
|
376
|
+
/**
|
|
377
|
+
* List published posts (summaries — no `html`).
|
|
378
|
+
*
|
|
379
|
+
* @example
|
|
380
|
+
* const { data } = await board.blog.posts.list({ tagSlug: 'news' });
|
|
381
|
+
*/
|
|
382
|
+
list(query, options) {
|
|
383
|
+
return client.fetch(
|
|
384
|
+
"/blog/posts",
|
|
385
|
+
{ ...options, query }
|
|
386
|
+
);
|
|
387
|
+
},
|
|
388
|
+
/**
|
|
389
|
+
* Retrieve one post by slug, including its `html` body.
|
|
390
|
+
*
|
|
391
|
+
* @example
|
|
392
|
+
* const post = await board.blog.posts.retrieve('hello-world');
|
|
393
|
+
*/
|
|
394
|
+
retrieve(postSlug, query, options) {
|
|
395
|
+
return client.fetch(
|
|
396
|
+
`/blog/posts/${encodeURIComponent(postSlug)}`,
|
|
397
|
+
{ ...options, query }
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
},
|
|
401
|
+
tags: {
|
|
402
|
+
/** @example const { data } = await board.blog.tags.list(); */
|
|
403
|
+
list(query, options) {
|
|
404
|
+
return client.fetch("/blog/tags", {
|
|
405
|
+
...options,
|
|
406
|
+
query
|
|
407
|
+
});
|
|
408
|
+
},
|
|
409
|
+
/** @example const tag = await board.blog.tags.retrieve('news'); */
|
|
410
|
+
retrieve(tagSlug, query, options) {
|
|
411
|
+
return client.fetch(
|
|
412
|
+
`/blog/tags/${encodeURIComponent(tagSlug)}`,
|
|
413
|
+
{ ...options, query }
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
authors: {
|
|
418
|
+
/** @example const { data } = await board.blog.authors.list(); */
|
|
419
|
+
list(query, options) {
|
|
420
|
+
return client.fetch("/blog/authors", {
|
|
421
|
+
...options,
|
|
422
|
+
query
|
|
423
|
+
});
|
|
424
|
+
},
|
|
425
|
+
/** @example const author = await board.blog.authors.retrieve('jane'); */
|
|
426
|
+
retrieve(authorSlug, query, options) {
|
|
427
|
+
return client.fetch(
|
|
428
|
+
`/blog/authors/${encodeURIComponent(authorSlug)}`,
|
|
429
|
+
{ ...options, query }
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
},
|
|
433
|
+
/**
|
|
434
|
+
* Free-text post search (returns post summaries).
|
|
435
|
+
*
|
|
436
|
+
* @example
|
|
437
|
+
* const { data } = await board.blog.search({ query: 'launch' });
|
|
438
|
+
*/
|
|
439
|
+
search(body, query, options) {
|
|
440
|
+
return client.fetch(
|
|
441
|
+
"/blog/search",
|
|
442
|
+
{ ...options, method: "POST", body, query }
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// src/namespaces/companies.ts
|
|
449
|
+
function companiesNamespace(client) {
|
|
450
|
+
return {
|
|
451
|
+
/**
|
|
452
|
+
* List companies on the board.
|
|
453
|
+
*
|
|
454
|
+
* @example
|
|
455
|
+
* const { data } = await board.companies.list({ limit: 20 });
|
|
456
|
+
*/
|
|
457
|
+
list(query, options) {
|
|
458
|
+
return client.fetch("/companies", {
|
|
459
|
+
...options,
|
|
460
|
+
query
|
|
461
|
+
});
|
|
462
|
+
},
|
|
463
|
+
/**
|
|
464
|
+
* Retrieve one company by slug.
|
|
465
|
+
*
|
|
466
|
+
* @example
|
|
467
|
+
* const company = await board.companies.retrieve('acme');
|
|
468
|
+
*/
|
|
469
|
+
retrieve(companySlug, query, options) {
|
|
470
|
+
return client.fetch(
|
|
471
|
+
`/companies/${encodeURIComponent(companySlug)}`,
|
|
472
|
+
{ ...options, query }
|
|
473
|
+
);
|
|
474
|
+
},
|
|
475
|
+
/**
|
|
476
|
+
* Free-text company search.
|
|
477
|
+
*
|
|
478
|
+
* @example
|
|
479
|
+
* const { data } = await board.companies.search({ query: 'acme' });
|
|
480
|
+
*/
|
|
481
|
+
search(body, query, options) {
|
|
482
|
+
return client.fetch("/companies/search", {
|
|
483
|
+
...options,
|
|
484
|
+
method: "POST",
|
|
485
|
+
body,
|
|
486
|
+
query
|
|
487
|
+
});
|
|
488
|
+
},
|
|
489
|
+
/**
|
|
490
|
+
* List one company's published jobs.
|
|
491
|
+
*
|
|
492
|
+
* @example
|
|
493
|
+
* const { data } = await board.companies.listJobs('acme', { limit: 10 });
|
|
494
|
+
*/
|
|
495
|
+
listJobs(companySlug, query, options) {
|
|
496
|
+
return client.fetch(
|
|
497
|
+
`/companies/${encodeURIComponent(companySlug)}/jobs`,
|
|
498
|
+
{ ...options, query }
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// src/namespaces/jobs.ts
|
|
505
|
+
function jobsNamespace(client) {
|
|
506
|
+
return {
|
|
507
|
+
/**
|
|
508
|
+
* List published jobs.
|
|
509
|
+
*
|
|
510
|
+
* @example
|
|
511
|
+
* const { data, nextCursor } = await board.jobs.list({ limit: 20 });
|
|
512
|
+
*/
|
|
513
|
+
list(query, options) {
|
|
514
|
+
return client.fetch("/jobs", {
|
|
515
|
+
...options,
|
|
516
|
+
query
|
|
517
|
+
});
|
|
518
|
+
},
|
|
519
|
+
/**
|
|
520
|
+
* Retrieve one published job by slug.
|
|
521
|
+
*
|
|
522
|
+
* @example
|
|
523
|
+
* const job = await board.jobs.retrieve('senior-chef');
|
|
524
|
+
*/
|
|
525
|
+
retrieve(jobSlug, query, options) {
|
|
526
|
+
return client.fetch(`/jobs/${encodeURIComponent(jobSlug)}`, {
|
|
527
|
+
...options,
|
|
528
|
+
query
|
|
529
|
+
});
|
|
530
|
+
},
|
|
531
|
+
/**
|
|
532
|
+
* Free-text + faceted job search.
|
|
533
|
+
*
|
|
534
|
+
* @example
|
|
535
|
+
* const { data } = await board.jobs.search({
|
|
536
|
+
* query: 'chef',
|
|
537
|
+
* filters: { seniority: ['senior'] },
|
|
538
|
+
* });
|
|
539
|
+
*/
|
|
540
|
+
search(body, query, options) {
|
|
541
|
+
return client.fetch("/jobs/search", {
|
|
542
|
+
...options,
|
|
543
|
+
method: "POST",
|
|
544
|
+
body,
|
|
545
|
+
query
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// src/namespaces/me.ts
|
|
552
|
+
function meNamespace(client) {
|
|
553
|
+
return {
|
|
554
|
+
/**
|
|
555
|
+
* Retrieve the authenticated board user.
|
|
556
|
+
*
|
|
557
|
+
* @example
|
|
558
|
+
* const me = await board.me.retrieve();
|
|
559
|
+
*/
|
|
560
|
+
// `query?: Record<string, never>` = no query params today; the slot
|
|
561
|
+
// holds the locked (id, query?, options?) shape and widens to a real
|
|
562
|
+
// query type without breaking callers when the API grows one.
|
|
563
|
+
retrieve(query, options) {
|
|
564
|
+
return client.fetch("/me", { ...options, query });
|
|
565
|
+
},
|
|
566
|
+
savedJobs: {
|
|
567
|
+
/**
|
|
568
|
+
* List the authenticated user's saved jobs (each embeds the full
|
|
569
|
+
* `public_job`).
|
|
570
|
+
*
|
|
571
|
+
* @example
|
|
572
|
+
* const { data } = await board.me.savedJobs.list({ limit: 20 });
|
|
573
|
+
*/
|
|
574
|
+
list(query, options) {
|
|
575
|
+
return client.fetch("/me/saved-jobs", {
|
|
576
|
+
...options,
|
|
577
|
+
query
|
|
578
|
+
});
|
|
579
|
+
},
|
|
580
|
+
/**
|
|
581
|
+
* Save a job. Converges — saving an already-saved job returns the
|
|
582
|
+
* identical row.
|
|
583
|
+
*
|
|
584
|
+
* @example
|
|
585
|
+
* await board.me.savedJobs.save({ jobId: job.id });
|
|
586
|
+
*/
|
|
587
|
+
save(body, query, options) {
|
|
588
|
+
return client.fetch("/me/saved-jobs", {
|
|
589
|
+
...options,
|
|
590
|
+
method: "POST",
|
|
591
|
+
body,
|
|
592
|
+
query
|
|
593
|
+
});
|
|
594
|
+
},
|
|
595
|
+
/**
|
|
596
|
+
* Unsave a job. Idempotent — unknown IDs still 204.
|
|
597
|
+
*
|
|
598
|
+
* @example
|
|
599
|
+
* await board.me.savedJobs.unsave(job.id);
|
|
600
|
+
*/
|
|
601
|
+
unsave(jobId, query, options) {
|
|
602
|
+
return client.fetch(
|
|
603
|
+
`/me/saved-jobs/${encodeURIComponent(jobId)}`,
|
|
604
|
+
{ ...options, method: "DELETE", query }
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// src/index.ts
|
|
612
|
+
function createBoardClient(options) {
|
|
613
|
+
const client = new BoardClient({
|
|
614
|
+
baseUrl: options.baseUrl,
|
|
615
|
+
board: options.board,
|
|
616
|
+
storage: resolveStorage(options.auth?.storage),
|
|
617
|
+
globalHeaders: options.globalHeaders,
|
|
618
|
+
onRequest: options.onRequest,
|
|
619
|
+
onResponse: options.onResponse,
|
|
620
|
+
logger: options.logger
|
|
621
|
+
});
|
|
622
|
+
return {
|
|
623
|
+
/**
|
|
624
|
+
* Escape hatch: raw typed request through the full pipeline (board
|
|
625
|
+
* base path, default headers, bearer token, hooks). Custom
|
|
626
|
+
* endpoints work without an SDK release.
|
|
627
|
+
*/
|
|
628
|
+
client,
|
|
629
|
+
/**
|
|
630
|
+
* Board context — identity, features, analytics, theme.
|
|
631
|
+
*
|
|
632
|
+
* @example
|
|
633
|
+
* const { name, theme } = await board.context();
|
|
634
|
+
*/
|
|
635
|
+
context(options2) {
|
|
636
|
+
return client.fetch("", options2);
|
|
637
|
+
},
|
|
638
|
+
jobs: jobsNamespace(client),
|
|
639
|
+
companies: companiesNamespace(client),
|
|
640
|
+
blog: blogNamespace(client),
|
|
641
|
+
auth: authNamespace(client),
|
|
642
|
+
me: meNamespace(client)
|
|
643
|
+
};
|
|
644
|
+
}
|