@cavuno/board 1.37.0 → 1.39.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 +7 -0
- package/dist/{jobs-CMCADU_-.d.mts → _spec-DxC1ze93.d.mts} +57 -100
- package/dist/{jobs-CMCADU_-.d.ts → _spec-DxC1ze93.d.ts} +57 -100
- package/dist/{board-D_BRa2zC.d.ts → board-CqYibYUA.d.mts} +1 -1
- package/dist/{board-D0guOBYy.d.mts → board-RfZEAJse.d.ts} +1 -1
- package/dist/filters.d.mts +18 -4
- package/dist/filters.d.ts +18 -4
- package/dist/filters.js +17 -0
- package/dist/filters.mjs +17 -0
- package/dist/format.d.mts +3 -2
- package/dist/format.d.ts +3 -2
- package/dist/index.d.mts +17 -73
- package/dist/index.d.ts +17 -73
- package/dist/index.js +22 -1
- package/dist/index.mjs +22 -1
- package/dist/jobs-CLLIvtMc.d.ts +105 -0
- package/dist/jobs-DPPA1Nev.d.mts +105 -0
- package/dist/{salaries-9U42CM5A.d.mts → salaries-DK4RnJnw.d.ts} +2 -1
- package/dist/{salaries-C3w9kvPJ.d.ts → salaries-Rb5h_eVZ.d.mts} +2 -1
- package/dist/search-CqBa1Qc4.d.mts +81 -0
- package/dist/search-DBoMM-gE.d.ts +81 -0
- package/dist/seo.d.mts +4 -3
- package/dist/seo.d.ts +4 -3
- package/dist/server.d.mts +5 -3
- package/dist/server.d.ts +5 -3
- package/dist/sitemap.d.mts +5 -3
- package/dist/sitemap.d.ts +5 -3
- package/dist/suggest.d.mts +70 -0
- package/dist/suggest.d.ts +70 -0
- package/dist/suggest.js +165 -0
- package/dist/suggest.mjs +144 -0
- package/package.json +11 -1
- package/skills/cavuno-board-filters/SKILL.md +11 -4
- package/skills/cavuno-board-jobs/SKILL.md +18 -2
- package/skills/cavuno-board-suggest/SKILL.md +119 -0
- package/skills/manifest.json +9 -2
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { S as Schemas } from './_spec-DxC1ze93.mjs';
|
|
2
|
+
|
|
3
|
+
type Awaitable<T> = T | Promise<T>;
|
|
4
|
+
/**
|
|
5
|
+
* Async token storage. The SDK reads the access token from storage on
|
|
6
|
+
* every request (fresh-token rule); auth methods write/clear the pair.
|
|
7
|
+
*/
|
|
8
|
+
interface CustomStorage {
|
|
9
|
+
getItem(key: string): Awaitable<string | null>;
|
|
10
|
+
setItem(key: string, value: string): Awaitable<void>;
|
|
11
|
+
removeItem(key: string): Awaitable<void>;
|
|
12
|
+
}
|
|
13
|
+
type StorageMode = 'memory' | 'nostore' | 'local' | 'session' | CustomStorage;
|
|
14
|
+
declare const ACCESS_TOKEN_KEY = "cavuno_board_access_token";
|
|
15
|
+
declare const REFRESH_TOKEN_KEY = "cavuno_board_refresh_token";
|
|
16
|
+
/** Board-password access grant — sent as `X-Board-Access` to pass the wall. */
|
|
17
|
+
declare const BOARD_ACCESS_GRANT_KEY = "cavuno_board_access_grant";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The request handed to `onRequest` and `fetch`. `onRequest` may
|
|
21
|
+
* mutate or replace it wholesale (locale headers, URL rewrites);
|
|
22
|
+
* whatever it returns is what gets fetched.
|
|
23
|
+
*/
|
|
24
|
+
interface BoardRequest {
|
|
25
|
+
/** Fully built URL, query string included. */
|
|
26
|
+
url: string;
|
|
27
|
+
/** Final RequestInit: method, Headers instance, serialized body, plus passthrough keys (`signal`, `cache`, `next`, `cf`, …). */
|
|
28
|
+
init: RequestInit;
|
|
29
|
+
}
|
|
30
|
+
interface Logger {
|
|
31
|
+
debug(message: string): void;
|
|
32
|
+
error(message: string): void;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Per-call options on every SDK method. Everything besides `body` and
|
|
36
|
+
* `query` is passed through to `fetch` untouched, so framework caching
|
|
37
|
+
* (`next: { tags }`, `cf: {...}`, `cache:`) rides for free.
|
|
38
|
+
*/
|
|
39
|
+
type FetchOptions = Omit<RequestInit, 'body'> & {
|
|
40
|
+
body?: unknown;
|
|
41
|
+
query?: Record<string, unknown>;
|
|
42
|
+
};
|
|
43
|
+
interface BoardClientOptions {
|
|
44
|
+
baseUrl: string;
|
|
45
|
+
/** Board identifier: `pk_…` key | `boards_…` ID | slug (ADR-0032). */
|
|
46
|
+
board: string;
|
|
47
|
+
storage: CustomStorage;
|
|
48
|
+
globalHeaders?: Record<string, string>;
|
|
49
|
+
onRequest?: (req: BoardRequest) => Awaitable<BoardRequest>;
|
|
50
|
+
onResponse?: (res: Response, req: BoardRequest) => Awaitable<void>;
|
|
51
|
+
logger?: Logger;
|
|
52
|
+
}
|
|
53
|
+
declare class BoardClient {
|
|
54
|
+
readonly storage: CustomStorage;
|
|
55
|
+
private readonly basePath;
|
|
56
|
+
private readonly options;
|
|
57
|
+
constructor(options: BoardClientOptions);
|
|
58
|
+
/**
|
|
59
|
+
* The full request pipeline. Public and first-class: custom endpoints
|
|
60
|
+
* work without an SDK release, and still get the board-identifier
|
|
61
|
+
* base path, default headers, bearer token, and both hooks.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* const stats = await board.client.fetch<MyShape>('/custom/stats');
|
|
65
|
+
*/
|
|
66
|
+
fetch<T>(path: string, init?: FetchOptions): Promise<T>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
type SuggestResult = Schemas['SuggestResult'];
|
|
70
|
+
type SuggestionItem = Schemas['SuggestionItem'];
|
|
71
|
+
type CompanySuggestion = Schemas['CompanySuggestion'];
|
|
72
|
+
type TermSuggestion = Schemas['TermSuggestion'];
|
|
73
|
+
/** Query for `board.search.suggest()`. */
|
|
74
|
+
type SearchSuggestQuery = {
|
|
75
|
+
/** Keyword; under 2 chars the server returns an empty `items` array. */
|
|
76
|
+
q?: string;
|
|
77
|
+
/** Max interleaved suggestions (1–25; default 25). */
|
|
78
|
+
limit?: number;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export { type Awaitable as A, type BoardRequest as B, type CompanySuggestion as C, type FetchOptions as F, type Logger as L, REFRESH_TOKEN_KEY as R, type SuggestionItem as S, type TermSuggestion as T, type SearchSuggestQuery as a, type SuggestResult as b, type StorageMode as c, BoardClient as d, ACCESS_TOKEN_KEY as e, BOARD_ACCESS_GRANT_KEY as f, type CustomStorage as g };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { S as Schemas } from './_spec-DxC1ze93.js';
|
|
2
|
+
|
|
3
|
+
type Awaitable<T> = T | Promise<T>;
|
|
4
|
+
/**
|
|
5
|
+
* Async token storage. The SDK reads the access token from storage on
|
|
6
|
+
* every request (fresh-token rule); auth methods write/clear the pair.
|
|
7
|
+
*/
|
|
8
|
+
interface CustomStorage {
|
|
9
|
+
getItem(key: string): Awaitable<string | null>;
|
|
10
|
+
setItem(key: string, value: string): Awaitable<void>;
|
|
11
|
+
removeItem(key: string): Awaitable<void>;
|
|
12
|
+
}
|
|
13
|
+
type StorageMode = 'memory' | 'nostore' | 'local' | 'session' | CustomStorage;
|
|
14
|
+
declare const ACCESS_TOKEN_KEY = "cavuno_board_access_token";
|
|
15
|
+
declare const REFRESH_TOKEN_KEY = "cavuno_board_refresh_token";
|
|
16
|
+
/** Board-password access grant — sent as `X-Board-Access` to pass the wall. */
|
|
17
|
+
declare const BOARD_ACCESS_GRANT_KEY = "cavuno_board_access_grant";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The request handed to `onRequest` and `fetch`. `onRequest` may
|
|
21
|
+
* mutate or replace it wholesale (locale headers, URL rewrites);
|
|
22
|
+
* whatever it returns is what gets fetched.
|
|
23
|
+
*/
|
|
24
|
+
interface BoardRequest {
|
|
25
|
+
/** Fully built URL, query string included. */
|
|
26
|
+
url: string;
|
|
27
|
+
/** Final RequestInit: method, Headers instance, serialized body, plus passthrough keys (`signal`, `cache`, `next`, `cf`, …). */
|
|
28
|
+
init: RequestInit;
|
|
29
|
+
}
|
|
30
|
+
interface Logger {
|
|
31
|
+
debug(message: string): void;
|
|
32
|
+
error(message: string): void;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Per-call options on every SDK method. Everything besides `body` and
|
|
36
|
+
* `query` is passed through to `fetch` untouched, so framework caching
|
|
37
|
+
* (`next: { tags }`, `cf: {...}`, `cache:`) rides for free.
|
|
38
|
+
*/
|
|
39
|
+
type FetchOptions = Omit<RequestInit, 'body'> & {
|
|
40
|
+
body?: unknown;
|
|
41
|
+
query?: Record<string, unknown>;
|
|
42
|
+
};
|
|
43
|
+
interface BoardClientOptions {
|
|
44
|
+
baseUrl: string;
|
|
45
|
+
/** Board identifier: `pk_…` key | `boards_…` ID | slug (ADR-0032). */
|
|
46
|
+
board: string;
|
|
47
|
+
storage: CustomStorage;
|
|
48
|
+
globalHeaders?: Record<string, string>;
|
|
49
|
+
onRequest?: (req: BoardRequest) => Awaitable<BoardRequest>;
|
|
50
|
+
onResponse?: (res: Response, req: BoardRequest) => Awaitable<void>;
|
|
51
|
+
logger?: Logger;
|
|
52
|
+
}
|
|
53
|
+
declare class BoardClient {
|
|
54
|
+
readonly storage: CustomStorage;
|
|
55
|
+
private readonly basePath;
|
|
56
|
+
private readonly options;
|
|
57
|
+
constructor(options: BoardClientOptions);
|
|
58
|
+
/**
|
|
59
|
+
* The full request pipeline. Public and first-class: custom endpoints
|
|
60
|
+
* work without an SDK release, and still get the board-identifier
|
|
61
|
+
* base path, default headers, bearer token, and both hooks.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* const stats = await board.client.fetch<MyShape>('/custom/stats');
|
|
65
|
+
*/
|
|
66
|
+
fetch<T>(path: string, init?: FetchOptions): Promise<T>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
type SuggestResult = Schemas['SuggestResult'];
|
|
70
|
+
type SuggestionItem = Schemas['SuggestionItem'];
|
|
71
|
+
type CompanySuggestion = Schemas['CompanySuggestion'];
|
|
72
|
+
type TermSuggestion = Schemas['TermSuggestion'];
|
|
73
|
+
/** Query for `board.search.suggest()`. */
|
|
74
|
+
type SearchSuggestQuery = {
|
|
75
|
+
/** Keyword; under 2 chars the server returns an empty `items` array. */
|
|
76
|
+
q?: string;
|
|
77
|
+
/** Max interleaved suggestions (1–25; default 25). */
|
|
78
|
+
limit?: number;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export { type Awaitable as A, type BoardRequest as B, type CompanySuggestion as C, type FetchOptions as F, type Logger as L, REFRESH_TOKEN_KEY as R, type SuggestionItem as S, type TermSuggestion as T, type SearchSuggestQuery as a, type SuggestResult as b, type StorageMode as c, BoardClient as d, ACCESS_TOKEN_KEY as e, BOARD_ACCESS_GRANT_KEY as f, type CustomStorage as g };
|
package/dist/seo.d.mts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { P as PublicBlogPostSummary, B as BlogAuthorEmbed, C as CompanyCategorySalary, a as CompanySalary, L as LocationSalaryDetail, S as SkillSalaryDetail, T as TitleSalaryDetail } from './salaries-
|
|
2
|
-
import { P as PublicBoard } from './board-
|
|
3
|
-
import { P as PublicJob } from './jobs-
|
|
1
|
+
import { P as PublicBlogPostSummary, B as BlogAuthorEmbed, C as CompanyCategorySalary, a as CompanySalary, L as LocationSalaryDetail, S as SkillSalaryDetail, T as TitleSalaryDetail } from './salaries-Rb5h_eVZ.mjs';
|
|
2
|
+
import { P as PublicBoard } from './board-CqYibYUA.mjs';
|
|
3
|
+
import { P as PublicJob } from './jobs-DPPA1Nev.mjs';
|
|
4
4
|
import { B as BoardLabelOverrides } from './ui-copy-CKfFTtLk.mjs';
|
|
5
|
+
import './_spec-DxC1ze93.mjs';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Google for Jobs `JobPosting` structured data on the `@cavuno/board` wire
|
package/dist/seo.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { P as PublicBlogPostSummary, B as BlogAuthorEmbed, C as CompanyCategorySalary, a as CompanySalary, L as LocationSalaryDetail, S as SkillSalaryDetail, T as TitleSalaryDetail } from './salaries-
|
|
2
|
-
import { P as PublicBoard } from './board-
|
|
3
|
-
import { P as PublicJob } from './jobs-
|
|
1
|
+
import { P as PublicBlogPostSummary, B as BlogAuthorEmbed, C as CompanyCategorySalary, a as CompanySalary, L as LocationSalaryDetail, S as SkillSalaryDetail, T as TitleSalaryDetail } from './salaries-DK4RnJnw.js';
|
|
2
|
+
import { P as PublicBoard } from './board-RfZEAJse.js';
|
|
3
|
+
import { P as PublicJob } from './jobs-CLLIvtMc.js';
|
|
4
4
|
import { B as BoardLabelOverrides } from './ui-copy-CKfFTtLk.js';
|
|
5
|
+
import './_spec-DxC1ze93.js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Google for Jobs `JobPosting` structured data on the `@cavuno/board` wire
|
package/dist/server.d.mts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { BoardSdk } from './index.mjs';
|
|
2
|
-
import './
|
|
3
|
-
import './
|
|
4
|
-
import './
|
|
2
|
+
import './_spec-DxC1ze93.mjs';
|
|
3
|
+
import './search-CqBa1Qc4.mjs';
|
|
4
|
+
import './board-CqYibYUA.mjs';
|
|
5
|
+
import './jobs-DPPA1Nev.mjs';
|
|
6
|
+
import './salaries-Rb5h_eVZ.mjs';
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Session cookie codec — pure (no framework imports, no node imports) so it
|
package/dist/server.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { BoardSdk } from './index.js';
|
|
2
|
-
import './
|
|
3
|
-
import './
|
|
4
|
-
import './
|
|
2
|
+
import './_spec-DxC1ze93.js';
|
|
3
|
+
import './search-DBoMM-gE.js';
|
|
4
|
+
import './board-RfZEAJse.js';
|
|
5
|
+
import './jobs-CLLIvtMc.js';
|
|
6
|
+
import './salaries-DK4RnJnw.js';
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Session cookie codec — pure (no framework imports, no node imports) so it
|
package/dist/sitemap.d.mts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { BoardSdk } from './index.mjs';
|
|
2
|
-
import './
|
|
3
|
-
import './
|
|
4
|
-
import './
|
|
2
|
+
import './_spec-DxC1ze93.mjs';
|
|
3
|
+
import './search-CqBa1Qc4.mjs';
|
|
4
|
+
import './board-CqYibYUA.mjs';
|
|
5
|
+
import './jobs-DPPA1Nev.mjs';
|
|
6
|
+
import './salaries-Rb5h_eVZ.mjs';
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Sitemap primitives — the pure XML + bucket-filename logic behind a board
|
package/dist/sitemap.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { BoardSdk } from './index.js';
|
|
2
|
-
import './
|
|
3
|
-
import './
|
|
4
|
-
import './
|
|
2
|
+
import './_spec-DxC1ze93.js';
|
|
3
|
+
import './search-DBoMM-gE.js';
|
|
4
|
+
import './board-RfZEAJse.js';
|
|
5
|
+
import './jobs-CLLIvtMc.js';
|
|
6
|
+
import './salaries-DK4RnJnw.js';
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Sitemap primitives — the pure XML + bucket-filename logic behind a board
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { S as SuggestionItem, a as SearchSuggestQuery, F as FetchOptions, b as SuggestResult } from './search-CqBa1Qc4.mjs';
|
|
2
|
+
import './_spec-DxC1ze93.mjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `@cavuno/board/suggest` — framework-agnostic headless search-suggest
|
|
6
|
+
* controller (Algolia autocomplete-core pattern).
|
|
7
|
+
*
|
|
8
|
+
* Owns debounce, abort, and stale-drop so tenant frontends don't re-invent
|
|
9
|
+
* them. Patches ship via npm update. Zero runtime deps; isomorphic
|
|
10
|
+
* (`globalThis` timers only — never `window`/`document`).
|
|
11
|
+
*
|
|
12
|
+
* The ONE permitted view-level filter is excluding currently-applied company
|
|
13
|
+
* slugs from results (type `company` only). Wire data is never reshaped
|
|
14
|
+
* otherwise — order stays server-ranked.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
interface SuggestControllerOptions {
|
|
18
|
+
/** Minimum characters before querying (default 2 — hosted parity). */
|
|
19
|
+
minChars?: number;
|
|
20
|
+
/** Debounce for setQuery → request (default 250ms). */
|
|
21
|
+
debounceMs?: number;
|
|
22
|
+
/** Server `limit` passed through (1–25). */
|
|
23
|
+
limit?: number;
|
|
24
|
+
}
|
|
25
|
+
type SuggestStatus = 'idle' | 'loading' | 'ready' | 'error';
|
|
26
|
+
interface SuggestState {
|
|
27
|
+
readonly query: string;
|
|
28
|
+
/**
|
|
29
|
+
* Server-ranked items. Previous items are RETAINED while a new request
|
|
30
|
+
* is loading (no flash-to-empty — hosted behavior).
|
|
31
|
+
*/
|
|
32
|
+
readonly items: readonly SuggestionItem[];
|
|
33
|
+
/**
|
|
34
|
+
* `'loading'` from the moment a qualifying query is set (debounce window
|
|
35
|
+
* included) until that query's request settles. `'idle'` only when the
|
|
36
|
+
* query is empty/below minChars. `'ready'` / `'error'` after settle.
|
|
37
|
+
*/
|
|
38
|
+
readonly status: SuggestStatus;
|
|
39
|
+
readonly error: unknown;
|
|
40
|
+
}
|
|
41
|
+
interface SuggestController {
|
|
42
|
+
setQuery(query: string): void;
|
|
43
|
+
/** Company slugs to hide from results (e.g. the currently-applied filter). */
|
|
44
|
+
setExcludedCompanySlugs(slugs: readonly string[]): void;
|
|
45
|
+
getState(): SuggestState;
|
|
46
|
+
/** Returns an unsubscribe function. Compatible with useSyncExternalStore. */
|
|
47
|
+
subscribe(listener: () => void): () => void;
|
|
48
|
+
dispose(): void;
|
|
49
|
+
}
|
|
50
|
+
type SuggestBoard = {
|
|
51
|
+
search: {
|
|
52
|
+
suggest(q?: SearchSuggestQuery, o?: FetchOptions): Promise<SuggestResult>;
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Create a headless suggest controller bound to a board client (or any
|
|
57
|
+
* object exposing `search.suggest`).
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* import { createBoardClient } from '@cavuno/board';
|
|
61
|
+
* import { createSuggestController } from '@cavuno/board/suggest';
|
|
62
|
+
*
|
|
63
|
+
* const board = createBoardClient({ board: 'pk_…' });
|
|
64
|
+
* const suggest = createSuggestController(board);
|
|
65
|
+
* suggest.subscribe(() => render(suggest.getState()));
|
|
66
|
+
* suggest.setQuery('acme');
|
|
67
|
+
*/
|
|
68
|
+
declare function createSuggestController(board: SuggestBoard, options?: SuggestControllerOptions): SuggestController;
|
|
69
|
+
|
|
70
|
+
export { type SuggestController, type SuggestControllerOptions, type SuggestState, type SuggestStatus, createSuggestController };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { S as SuggestionItem, a as SearchSuggestQuery, F as FetchOptions, b as SuggestResult } from './search-DBoMM-gE.js';
|
|
2
|
+
import './_spec-DxC1ze93.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `@cavuno/board/suggest` — framework-agnostic headless search-suggest
|
|
6
|
+
* controller (Algolia autocomplete-core pattern).
|
|
7
|
+
*
|
|
8
|
+
* Owns debounce, abort, and stale-drop so tenant frontends don't re-invent
|
|
9
|
+
* them. Patches ship via npm update. Zero runtime deps; isomorphic
|
|
10
|
+
* (`globalThis` timers only — never `window`/`document`).
|
|
11
|
+
*
|
|
12
|
+
* The ONE permitted view-level filter is excluding currently-applied company
|
|
13
|
+
* slugs from results (type `company` only). Wire data is never reshaped
|
|
14
|
+
* otherwise — order stays server-ranked.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
interface SuggestControllerOptions {
|
|
18
|
+
/** Minimum characters before querying (default 2 — hosted parity). */
|
|
19
|
+
minChars?: number;
|
|
20
|
+
/** Debounce for setQuery → request (default 250ms). */
|
|
21
|
+
debounceMs?: number;
|
|
22
|
+
/** Server `limit` passed through (1–25). */
|
|
23
|
+
limit?: number;
|
|
24
|
+
}
|
|
25
|
+
type SuggestStatus = 'idle' | 'loading' | 'ready' | 'error';
|
|
26
|
+
interface SuggestState {
|
|
27
|
+
readonly query: string;
|
|
28
|
+
/**
|
|
29
|
+
* Server-ranked items. Previous items are RETAINED while a new request
|
|
30
|
+
* is loading (no flash-to-empty — hosted behavior).
|
|
31
|
+
*/
|
|
32
|
+
readonly items: readonly SuggestionItem[];
|
|
33
|
+
/**
|
|
34
|
+
* `'loading'` from the moment a qualifying query is set (debounce window
|
|
35
|
+
* included) until that query's request settles. `'idle'` only when the
|
|
36
|
+
* query is empty/below minChars. `'ready'` / `'error'` after settle.
|
|
37
|
+
*/
|
|
38
|
+
readonly status: SuggestStatus;
|
|
39
|
+
readonly error: unknown;
|
|
40
|
+
}
|
|
41
|
+
interface SuggestController {
|
|
42
|
+
setQuery(query: string): void;
|
|
43
|
+
/** Company slugs to hide from results (e.g. the currently-applied filter). */
|
|
44
|
+
setExcludedCompanySlugs(slugs: readonly string[]): void;
|
|
45
|
+
getState(): SuggestState;
|
|
46
|
+
/** Returns an unsubscribe function. Compatible with useSyncExternalStore. */
|
|
47
|
+
subscribe(listener: () => void): () => void;
|
|
48
|
+
dispose(): void;
|
|
49
|
+
}
|
|
50
|
+
type SuggestBoard = {
|
|
51
|
+
search: {
|
|
52
|
+
suggest(q?: SearchSuggestQuery, o?: FetchOptions): Promise<SuggestResult>;
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Create a headless suggest controller bound to a board client (or any
|
|
57
|
+
* object exposing `search.suggest`).
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* import { createBoardClient } from '@cavuno/board';
|
|
61
|
+
* import { createSuggestController } from '@cavuno/board/suggest';
|
|
62
|
+
*
|
|
63
|
+
* const board = createBoardClient({ board: 'pk_…' });
|
|
64
|
+
* const suggest = createSuggestController(board);
|
|
65
|
+
* suggest.subscribe(() => render(suggest.getState()));
|
|
66
|
+
* suggest.setQuery('acme');
|
|
67
|
+
*/
|
|
68
|
+
declare function createSuggestController(board: SuggestBoard, options?: SuggestControllerOptions): SuggestController;
|
|
69
|
+
|
|
70
|
+
export { type SuggestController, type SuggestControllerOptions, type SuggestState, type SuggestStatus, createSuggestController };
|
package/dist/suggest.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
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/suggest/index.ts
|
|
21
|
+
var suggest_exports = {};
|
|
22
|
+
__export(suggest_exports, {
|
|
23
|
+
createSuggestController: () => createSuggestController
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(suggest_exports);
|
|
26
|
+
var DEFAULT_MIN_CHARS = 2;
|
|
27
|
+
var DEFAULT_DEBOUNCE_MS = 250;
|
|
28
|
+
function isAbortError(error) {
|
|
29
|
+
return typeof DOMException !== "undefined" && error instanceof DOMException && error.name === "AbortError" || error instanceof Error && error.name === "AbortError";
|
|
30
|
+
}
|
|
31
|
+
function applyExclusions(items, excluded) {
|
|
32
|
+
if (excluded.size === 0) return items.slice();
|
|
33
|
+
return items.filter(
|
|
34
|
+
(item) => !(item.type === "company" && excluded.has(item.slug.toLowerCase()))
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
function createSuggestController(board, options) {
|
|
38
|
+
const minChars = options?.minChars ?? DEFAULT_MIN_CHARS;
|
|
39
|
+
const debounceMs = options?.debounceMs ?? DEFAULT_DEBOUNCE_MS;
|
|
40
|
+
const limit = options?.limit;
|
|
41
|
+
let disposed = false;
|
|
42
|
+
let state = {
|
|
43
|
+
query: "",
|
|
44
|
+
items: [],
|
|
45
|
+
status: "idle",
|
|
46
|
+
error: null
|
|
47
|
+
};
|
|
48
|
+
let rawItems = [];
|
|
49
|
+
let excluded = /* @__PURE__ */ new Set();
|
|
50
|
+
let sequence = 0;
|
|
51
|
+
let timer = null;
|
|
52
|
+
let abortController = null;
|
|
53
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
54
|
+
function notify() {
|
|
55
|
+
for (const listener of listeners) listener();
|
|
56
|
+
}
|
|
57
|
+
function setState(next) {
|
|
58
|
+
state = next;
|
|
59
|
+
notify();
|
|
60
|
+
}
|
|
61
|
+
function cancelTimer() {
|
|
62
|
+
if (timer !== null) {
|
|
63
|
+
globalThis.clearTimeout(timer);
|
|
64
|
+
timer = null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function abortInFlight() {
|
|
68
|
+
if (abortController) {
|
|
69
|
+
abortController.abort();
|
|
70
|
+
abortController = null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function runRequest(query) {
|
|
74
|
+
const seq = ++sequence;
|
|
75
|
+
abortInFlight();
|
|
76
|
+
const controller = new AbortController();
|
|
77
|
+
abortController = controller;
|
|
78
|
+
const suggestQuery = { q: query };
|
|
79
|
+
if (limit !== void 0) suggestQuery.limit = limit;
|
|
80
|
+
void board.search.suggest(suggestQuery, { signal: controller.signal }).then((result) => {
|
|
81
|
+
if (disposed || seq !== sequence) return;
|
|
82
|
+
abortController = null;
|
|
83
|
+
rawItems = result.items;
|
|
84
|
+
setState({
|
|
85
|
+
query: state.query,
|
|
86
|
+
items: applyExclusions(rawItems, excluded),
|
|
87
|
+
status: "ready",
|
|
88
|
+
error: null
|
|
89
|
+
});
|
|
90
|
+
}).catch((error) => {
|
|
91
|
+
if (disposed || seq !== sequence) return;
|
|
92
|
+
if (isAbortError(error)) return;
|
|
93
|
+
abortController = null;
|
|
94
|
+
setState({
|
|
95
|
+
query: state.query,
|
|
96
|
+
items: state.items,
|
|
97
|
+
status: "error",
|
|
98
|
+
error
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
setQuery(query) {
|
|
104
|
+
if (disposed) return;
|
|
105
|
+
const trimmed = query.trim();
|
|
106
|
+
cancelTimer();
|
|
107
|
+
if (trimmed.length < minChars) {
|
|
108
|
+
sequence += 1;
|
|
109
|
+
abortInFlight();
|
|
110
|
+
rawItems = [];
|
|
111
|
+
setState({
|
|
112
|
+
query,
|
|
113
|
+
items: [],
|
|
114
|
+
status: "idle",
|
|
115
|
+
error: null
|
|
116
|
+
});
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
sequence += 1;
|
|
120
|
+
abortInFlight();
|
|
121
|
+
setState({
|
|
122
|
+
query,
|
|
123
|
+
items: state.items,
|
|
124
|
+
status: "loading",
|
|
125
|
+
error: null
|
|
126
|
+
});
|
|
127
|
+
timer = globalThis.setTimeout(() => {
|
|
128
|
+
timer = null;
|
|
129
|
+
if (disposed) return;
|
|
130
|
+
runRequest(trimmed);
|
|
131
|
+
}, debounceMs);
|
|
132
|
+
},
|
|
133
|
+
setExcludedCompanySlugs(slugs) {
|
|
134
|
+
if (disposed) return;
|
|
135
|
+
excluded = new Set(
|
|
136
|
+
slugs.map((s) => s.trim().toLowerCase()).filter(Boolean)
|
|
137
|
+
);
|
|
138
|
+
setState({
|
|
139
|
+
query: state.query,
|
|
140
|
+
items: applyExclusions(rawItems, excluded),
|
|
141
|
+
status: state.status,
|
|
142
|
+
error: state.error
|
|
143
|
+
});
|
|
144
|
+
},
|
|
145
|
+
getState() {
|
|
146
|
+
return state;
|
|
147
|
+
},
|
|
148
|
+
subscribe(listener) {
|
|
149
|
+
if (disposed) return () => {
|
|
150
|
+
};
|
|
151
|
+
listeners.add(listener);
|
|
152
|
+
return () => {
|
|
153
|
+
listeners.delete(listener);
|
|
154
|
+
};
|
|
155
|
+
},
|
|
156
|
+
dispose() {
|
|
157
|
+
if (disposed) return;
|
|
158
|
+
disposed = true;
|
|
159
|
+
cancelTimer();
|
|
160
|
+
abortInFlight();
|
|
161
|
+
sequence += 1;
|
|
162
|
+
listeners.clear();
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|