@brownandroot/api 0.12.0 → 0.14.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.ts CHANGED
@@ -18,10 +18,15 @@ export interface Employee {
18
18
  payClass: string | null;
19
19
  jobType: string | null;
20
20
  jobStep: string | null;
21
+ jobDescription: string | null;
21
22
  workSchedule: string | null;
22
23
  shift: string | null;
24
+ hourlyRate: string | null;
25
+ annualSalary: string | null;
23
26
  termDate: string | null;
24
27
  hireDate: string | null;
28
+ topFlexPtoDate: string | null;
29
+ clientPtoDate: string | null;
25
30
  securityLevel: string | null;
26
31
  reportingLevel: string | null;
27
32
  recordType: string | null;
@@ -30,6 +35,11 @@ export interface Employee {
30
35
  supervisor: string | null;
31
36
  adjustedServiceDate: string | null;
32
37
  departmentCode: string | null;
38
+ division: string | null;
39
+ sector: string | null;
40
+ subsector: string | null;
41
+ phone: string | null;
42
+ identityHash: string | null;
33
43
  nccerNumber: string | null;
34
44
  mentor: string | null;
35
45
  source: string | null;
@@ -40,6 +50,8 @@ export interface Employee {
40
50
  export interface ApiHubClientOptions {
41
51
  baseUrl: string;
42
52
  apiKey: string;
53
+ /** Default cache TTL in milliseconds. Set to 0 to disable caching. Default: 5 minutes. */
54
+ cacheTtl?: number;
43
55
  }
44
56
  export interface DropdownOption {
45
57
  value: number | string;
@@ -81,6 +93,38 @@ export interface ChatResponse {
81
93
  totalTokens: number | null;
82
94
  };
83
95
  }
96
+ export interface DocumentRecord {
97
+ id: number;
98
+ fileName: string;
99
+ fileType: 'pdf' | 'csv';
100
+ documentType: string | null;
101
+ summary: string | null;
102
+ totalChunks: number;
103
+ uploadedAt: string | null;
104
+ uploadedBy: string | null;
105
+ }
106
+ export interface SearchResult {
107
+ chunkId: number;
108
+ content: string;
109
+ fileName: string;
110
+ documentType: string | null;
111
+ score: number;
112
+ }
113
+ export interface StreamChatUserContext {
114
+ name: string;
115
+ department?: string;
116
+ roles?: string[];
117
+ }
118
+ export interface StreamChatRequest {
119
+ messages: {
120
+ role: 'user' | 'assistant';
121
+ content: string;
122
+ }[];
123
+ userContext?: StreamChatUserContext;
124
+ useRag?: boolean;
125
+ tools?: string[];
126
+ source?: string;
127
+ }
84
128
  export interface BusinessUnit {
85
129
  id: string;
86
130
  companyId: string;
@@ -158,7 +202,12 @@ export interface Jobtypejobstep {
158
202
  export declare class ApiHubClient {
159
203
  private baseUrl;
160
204
  private apiKey;
205
+ private cacheTtl;
206
+ private cache;
161
207
  constructor(options: ApiHubClientOptions);
208
+ /** Clear all cached responses, or a specific path */
209
+ clearCache(path?: string): void;
210
+ private cachedRequest;
162
211
  private request;
163
212
  /** Get all employees */
164
213
  getEmployees(): Promise<Employee[]>;
@@ -179,10 +228,34 @@ export declare class ApiHubClient {
179
228
  jde: string | null;
180
229
  employee: Employee | null;
181
230
  }>;
231
+ /** Search employees by home business unit (case-insensitive partial match) */
232
+ searchByHbu(hbu: string): Promise<Employee[]>;
233
+ /**
234
+ * Verify an employee's identity using the canonical identity hash.
235
+ * Inputs are hashed server-side and compared — no PII is stored or returned.
236
+ */
237
+ verifyIdentity(employeeId: string, inputs: {
238
+ first3FirstName: string;
239
+ first3LastName: string;
240
+ dob: string;
241
+ ssn4: string;
242
+ }): Promise<{
243
+ verified: boolean;
244
+ }>;
182
245
  /** List all LLM log entries (newest first) */
183
246
  getLlmLogs(): Promise<LlmLog[]>;
184
247
  /** Send a chat completion request to the LLM */
185
248
  chat(request: ChatRequest): Promise<ChatResponse>;
249
+ /** Start a streaming chat session — returns the raw SSE Response for proxying */
250
+ chatStream(request: StreamChatRequest): Promise<Response>;
251
+ /** Upload and process a document */
252
+ uploadDocument(fileName: string, base64Content: string, uploadedBy: string): Promise<DocumentRecord>;
253
+ /** List all documents in the knowledge base */
254
+ listDocuments(): Promise<DocumentRecord[]>;
255
+ /** Delete a document and its chunks */
256
+ deleteDocument(id: number): Promise<void>;
257
+ /** Search the knowledge base with a natural language query */
258
+ searchDocuments(query: string, topK?: number): Promise<SearchResult[]>;
186
259
  getBusinessUnits(): Promise<BusinessUnit[]>;
187
260
  getBusinessUnitsDropdown(): Promise<DropdownOption[]>;
188
261
  getBusinessUnit(id: string): Promise<BusinessUnit>;
package/dist/index.js CHANGED
@@ -1,9 +1,35 @@
1
1
  export class ApiHubClient {
2
2
  baseUrl;
3
3
  apiKey;
4
+ cacheTtl;
5
+ cache = new Map();
4
6
  constructor(options) {
5
7
  this.baseUrl = options.baseUrl.replace(/\/+$/, '');
6
8
  this.apiKey = options.apiKey;
9
+ this.cacheTtl = options.cacheTtl ?? 5 * 60 * 1000;
10
+ }
11
+ /** Clear all cached responses, or a specific path */
12
+ clearCache(path) {
13
+ if (path) {
14
+ this.cache.delete(path);
15
+ }
16
+ else {
17
+ this.cache.clear();
18
+ }
19
+ }
20
+ async cachedRequest(path, ttl) {
21
+ const effectiveTtl = ttl ?? this.cacheTtl;
22
+ if (effectiveTtl > 0) {
23
+ const entry = this.cache.get(path);
24
+ if (entry && entry.expires > Date.now()) {
25
+ return entry.data;
26
+ }
27
+ }
28
+ const data = await this.request(path);
29
+ if (effectiveTtl > 0) {
30
+ this.cache.set(path, { data, expires: Date.now() + effectiveTtl });
31
+ }
32
+ return data;
7
33
  }
8
34
  async request(path) {
9
35
  let res;
@@ -25,11 +51,11 @@ export class ApiHubClient {
25
51
  }
26
52
  /** Get all employees */
27
53
  async getEmployees() {
28
- return this.request('/employees');
54
+ return this.cachedRequest('/employees');
29
55
  }
30
56
  /** Get employees formatted for dropdown controls */
31
57
  async getEmployeesDropdown() {
32
- return this.request('/employees/dropdown');
58
+ return this.cachedRequest('/employees/dropdown');
33
59
  }
34
60
  /** Get a single employee by employeeId */
35
61
  async getEmployee(employeeId) {
@@ -55,12 +81,40 @@ export class ApiHubClient {
55
81
  async getJdeFromEmail(email) {
56
82
  return this.request(`/employees/jde-from-email/${encodeURIComponent(email)}`);
57
83
  }
84
+ /** Search employees by home business unit (case-insensitive partial match) */
85
+ async searchByHbu(hbu) {
86
+ return this.request(`/employees/search?hbu=${encodeURIComponent(hbu)}`);
87
+ }
88
+ /**
89
+ * Verify an employee's identity using the canonical identity hash.
90
+ * Inputs are hashed server-side and compared — no PII is stored or returned.
91
+ */
92
+ async verifyIdentity(employeeId, inputs) {
93
+ let res;
94
+ try {
95
+ res = await fetch(`${this.baseUrl}/employees/${encodeURIComponent(employeeId)}/verify-identity`, {
96
+ method: 'POST',
97
+ headers: { 'x-api-key': this.apiKey, 'Content-Type': 'application/json' },
98
+ body: JSON.stringify(inputs),
99
+ });
100
+ }
101
+ catch {
102
+ throw new Error(`APIHub unavailable: ${this.baseUrl}/employees/${employeeId}/verify-identity`);
103
+ }
104
+ if (!res.ok) {
105
+ const body = await res.json().catch(() => null);
106
+ const message = (body && typeof body === 'object' && 'error' in body && typeof body.error === 'string' ? body.error : null) ??
107
+ `Request failed: ${res.status} ${res.statusText}`;
108
+ throw new Error(message);
109
+ }
110
+ return res.json();
111
+ }
58
112
  // -----------------------------------------------------------------------
59
113
  // LLM Logs
60
114
  // -----------------------------------------------------------------------
61
115
  /** List all LLM log entries (newest first) */
62
116
  async getLlmLogs() {
63
- return this.request('/llm-logs');
117
+ return this.cachedRequest('/llm-logs');
64
118
  }
65
119
  /** Send a chat completion request to the LLM */
66
120
  async chat(request) {
@@ -87,13 +141,114 @@ export class ApiHubClient {
87
141
  return res.json();
88
142
  }
89
143
  // -----------------------------------------------------------------------
144
+ // Streaming Chat
145
+ // -----------------------------------------------------------------------
146
+ /** Start a streaming chat session — returns the raw SSE Response for proxying */
147
+ async chatStream(request) {
148
+ let res;
149
+ try {
150
+ res = await fetch(`${this.baseUrl}/ai/chat/stream`, {
151
+ method: 'POST',
152
+ headers: {
153
+ 'x-api-key': this.apiKey,
154
+ 'Content-Type': 'application/json',
155
+ },
156
+ body: JSON.stringify(request),
157
+ });
158
+ }
159
+ catch {
160
+ throw new Error(`APIHub unavailable: ${this.baseUrl}/ai/chat/stream`);
161
+ }
162
+ if (!res.ok) {
163
+ const body = await res.json().catch(() => null);
164
+ const message = (body && typeof body === 'object' && 'error' in body && typeof body.error === 'string' ? body.error : null) ??
165
+ `Request failed: ${res.status} ${res.statusText}`;
166
+ throw new Error(message);
167
+ }
168
+ return res;
169
+ }
170
+ // -----------------------------------------------------------------------
171
+ // RAG Documents
172
+ // -----------------------------------------------------------------------
173
+ /** Upload and process a document */
174
+ async uploadDocument(fileName, base64Content, uploadedBy) {
175
+ let res;
176
+ try {
177
+ res = await fetch(`${this.baseUrl}/ai/documents`, {
178
+ method: 'POST',
179
+ headers: {
180
+ 'x-api-key': this.apiKey,
181
+ 'Content-Type': 'application/json',
182
+ },
183
+ body: JSON.stringify({ fileName, base64Content, uploadedBy }),
184
+ });
185
+ }
186
+ catch {
187
+ throw new Error(`APIHub unavailable: ${this.baseUrl}/ai/documents`);
188
+ }
189
+ if (!res.ok) {
190
+ const body = await res.json().catch(() => null);
191
+ const message = (body && typeof body === 'object' && 'error' in body && typeof body.error === 'string' ? body.error : null) ??
192
+ `Request failed: ${res.status} ${res.statusText}`;
193
+ throw new Error(message);
194
+ }
195
+ return res.json();
196
+ }
197
+ /** List all documents in the knowledge base */
198
+ async listDocuments() {
199
+ return this.cachedRequest('/ai/documents');
200
+ }
201
+ /** Delete a document and its chunks */
202
+ async deleteDocument(id) {
203
+ let res;
204
+ try {
205
+ res = await fetch(`${this.baseUrl}/ai/documents/${id}`, {
206
+ method: 'DELETE',
207
+ headers: { 'x-api-key': this.apiKey },
208
+ });
209
+ }
210
+ catch {
211
+ throw new Error(`APIHub unavailable: ${this.baseUrl}/ai/documents/${id}`);
212
+ }
213
+ if (!res.ok) {
214
+ const body = await res.json().catch(() => null);
215
+ const message = (body && typeof body === 'object' && 'error' in body && typeof body.error === 'string' ? body.error : null) ??
216
+ `Request failed: ${res.status} ${res.statusText}`;
217
+ throw new Error(message);
218
+ }
219
+ }
220
+ /** Search the knowledge base with a natural language query */
221
+ async searchDocuments(query, topK) {
222
+ let res;
223
+ try {
224
+ res = await fetch(`${this.baseUrl}/ai/search`, {
225
+ method: 'POST',
226
+ headers: {
227
+ 'x-api-key': this.apiKey,
228
+ 'Content-Type': 'application/json',
229
+ },
230
+ body: JSON.stringify({ query, topK }),
231
+ });
232
+ }
233
+ catch {
234
+ throw new Error(`APIHub unavailable: ${this.baseUrl}/ai/search`);
235
+ }
236
+ if (!res.ok) {
237
+ const body = await res.json().catch(() => null);
238
+ const message = (body && typeof body === 'object' && 'error' in body && typeof body.error === 'string' ? body.error : null) ??
239
+ `Request failed: ${res.status} ${res.statusText}`;
240
+ throw new Error(message);
241
+ }
242
+ return res.json();
243
+ }
244
+ // -----------------------------------------------------------------------
90
245
  // Business Units
91
246
  // -----------------------------------------------------------------------
92
247
  async getBusinessUnits() {
93
- return this.request('/business-units');
248
+ return this.cachedRequest('/business-units');
94
249
  }
95
250
  async getBusinessUnitsDropdown() {
96
- return this.request('/business-units/dropdown');
251
+ return this.cachedRequest('/business-units/dropdown');
97
252
  }
98
253
  async getBusinessUnit(id) {
99
254
  return this.request(`/business-units/${encodeURIComponent(id)}`);
@@ -102,30 +257,30 @@ export class ApiHubClient {
102
257
  // Cost Codes
103
258
  // -----------------------------------------------------------------------
104
259
  async getCostcodes() {
105
- return this.request('/costcodes');
260
+ return this.cachedRequest('/costcodes');
106
261
  }
107
262
  async getCostcodesDropdown() {
108
- return this.request('/costcodes/dropdown');
263
+ return this.cachedRequest('/costcodes/dropdown');
109
264
  }
110
265
  async getCostcode(id) {
111
266
  return this.request(`/costcodes/${encodeURIComponent(id)}`);
112
267
  }
113
268
  /** Get cost codes for a specific business unit, formatted for dropdown controls */
114
269
  async getCostcodesDropdownByBu(businessUnitId) {
115
- return this.request(`/costcodes/by-bu/${encodeURIComponent(businessUnitId)}/dropdown`);
270
+ return this.cachedRequest(`/costcodes/by-bu/${encodeURIComponent(businessUnitId)}/dropdown`);
116
271
  }
117
272
  /** Get cost codes for a specific business unit and pay type, formatted for dropdown controls */
118
273
  async getCostcodesDropdownByBuAndPayType(businessUnitId, payTypeCode) {
119
- return this.request(`/costcodes/by-bu/${encodeURIComponent(businessUnitId)}/by-paytype/${encodeURIComponent(payTypeCode)}/dropdown`);
274
+ return this.cachedRequest(`/costcodes/by-bu/${encodeURIComponent(businessUnitId)}/by-paytype/${encodeURIComponent(payTypeCode)}/dropdown`);
120
275
  }
121
276
  // -----------------------------------------------------------------------
122
277
  // Pay Types
123
278
  // -----------------------------------------------------------------------
124
279
  async getPaytypes() {
125
- return this.request('/paytypes');
280
+ return this.cachedRequest('/paytypes');
126
281
  }
127
282
  async getPaytypesDropdown() {
128
- return this.request('/paytypes/dropdown');
283
+ return this.cachedRequest('/paytypes/dropdown');
129
284
  }
130
285
  async getPaytype(id) {
131
286
  return this.request(`/paytypes/${encodeURIComponent(id)}`);
@@ -134,14 +289,14 @@ export class ApiHubClient {
134
289
  // Work Orders
135
290
  // -----------------------------------------------------------------------
136
291
  async getWorkorders() {
137
- return this.request('/workorders');
292
+ return this.cachedRequest('/workorders');
138
293
  }
139
294
  async getWorkordersDropdown() {
140
- return this.request('/workorders/dropdown');
295
+ return this.cachedRequest('/workorders/dropdown');
141
296
  }
142
297
  /** Get work orders for a specific business unit, formatted for dropdown controls */
143
298
  async getWorkordersDropdownByBu(businessUnitId) {
144
- return this.request(`/workorders/by-bu/${encodeURIComponent(businessUnitId)}/dropdown`);
299
+ return this.cachedRequest(`/workorders/by-bu/${encodeURIComponent(businessUnitId)}/dropdown`);
145
300
  }
146
301
  async getWorkorder(id) {
147
302
  return this.request(`/workorders/${encodeURIComponent(id)}`);
@@ -150,10 +305,10 @@ export class ApiHubClient {
150
305
  // Job Type / Job Steps
151
306
  // -----------------------------------------------------------------------
152
307
  async getJobtypejobsteps() {
153
- return this.request('/jobtypejobsteps');
308
+ return this.cachedRequest('/jobtypejobsteps');
154
309
  }
155
310
  async getJobtypejobstepsDropdown() {
156
- return this.request('/jobtypejobsteps/dropdown');
311
+ return this.cachedRequest('/jobtypejobsteps/dropdown');
157
312
  }
158
313
  async getJobtypejobstep(id) {
159
314
  return this.request(`/jobtypejobsteps/${encodeURIComponent(id)}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brownandroot/api",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",