@brownandroot/api 0.12.0 → 0.13.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
@@ -40,6 +40,8 @@ export interface Employee {
40
40
  export interface ApiHubClientOptions {
41
41
  baseUrl: string;
42
42
  apiKey: string;
43
+ /** Default cache TTL in milliseconds. Set to 0 to disable caching. Default: 5 minutes. */
44
+ cacheTtl?: number;
43
45
  }
44
46
  export interface DropdownOption {
45
47
  value: number | string;
@@ -81,6 +83,38 @@ export interface ChatResponse {
81
83
  totalTokens: number | null;
82
84
  };
83
85
  }
86
+ export interface DocumentRecord {
87
+ id: number;
88
+ fileName: string;
89
+ fileType: 'pdf' | 'csv';
90
+ documentType: string | null;
91
+ summary: string | null;
92
+ totalChunks: number;
93
+ uploadedAt: string | null;
94
+ uploadedBy: string | null;
95
+ }
96
+ export interface SearchResult {
97
+ chunkId: number;
98
+ content: string;
99
+ fileName: string;
100
+ documentType: string | null;
101
+ score: number;
102
+ }
103
+ export interface StreamChatUserContext {
104
+ name: string;
105
+ department?: string;
106
+ roles?: string[];
107
+ }
108
+ export interface StreamChatRequest {
109
+ messages: {
110
+ role: 'user' | 'assistant';
111
+ content: string;
112
+ }[];
113
+ userContext?: StreamChatUserContext;
114
+ useRag?: boolean;
115
+ tools?: string[];
116
+ source?: string;
117
+ }
84
118
  export interface BusinessUnit {
85
119
  id: string;
86
120
  companyId: string;
@@ -158,7 +192,12 @@ export interface Jobtypejobstep {
158
192
  export declare class ApiHubClient {
159
193
  private baseUrl;
160
194
  private apiKey;
195
+ private cacheTtl;
196
+ private cache;
161
197
  constructor(options: ApiHubClientOptions);
198
+ /** Clear all cached responses, or a specific path */
199
+ clearCache(path?: string): void;
200
+ private cachedRequest;
162
201
  private request;
163
202
  /** Get all employees */
164
203
  getEmployees(): Promise<Employee[]>;
@@ -183,6 +222,16 @@ export declare class ApiHubClient {
183
222
  getLlmLogs(): Promise<LlmLog[]>;
184
223
  /** Send a chat completion request to the LLM */
185
224
  chat(request: ChatRequest): Promise<ChatResponse>;
225
+ /** Start a streaming chat session — returns the raw SSE Response for proxying */
226
+ chatStream(request: StreamChatRequest): Promise<Response>;
227
+ /** Upload and process a document */
228
+ uploadDocument(fileName: string, base64Content: string, uploadedBy: string): Promise<DocumentRecord>;
229
+ /** List all documents in the knowledge base */
230
+ listDocuments(): Promise<DocumentRecord[]>;
231
+ /** Delete a document and its chunks */
232
+ deleteDocument(id: number): Promise<void>;
233
+ /** Search the knowledge base with a natural language query */
234
+ searchDocuments(query: string, topK?: number): Promise<SearchResult[]>;
186
235
  getBusinessUnits(): Promise<BusinessUnit[]>;
187
236
  getBusinessUnitsDropdown(): Promise<DropdownOption[]>;
188
237
  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) {
@@ -60,7 +86,7 @@ export class ApiHubClient {
60
86
  // -----------------------------------------------------------------------
61
87
  /** List all LLM log entries (newest first) */
62
88
  async getLlmLogs() {
63
- return this.request('/llm-logs');
89
+ return this.cachedRequest('/llm-logs');
64
90
  }
65
91
  /** Send a chat completion request to the LLM */
66
92
  async chat(request) {
@@ -87,13 +113,114 @@ export class ApiHubClient {
87
113
  return res.json();
88
114
  }
89
115
  // -----------------------------------------------------------------------
116
+ // Streaming Chat
117
+ // -----------------------------------------------------------------------
118
+ /** Start a streaming chat session — returns the raw SSE Response for proxying */
119
+ async chatStream(request) {
120
+ let res;
121
+ try {
122
+ res = await fetch(`${this.baseUrl}/ai/chat/stream`, {
123
+ method: 'POST',
124
+ headers: {
125
+ 'x-api-key': this.apiKey,
126
+ 'Content-Type': 'application/json',
127
+ },
128
+ body: JSON.stringify(request),
129
+ });
130
+ }
131
+ catch {
132
+ throw new Error(`APIHub unavailable: ${this.baseUrl}/ai/chat/stream`);
133
+ }
134
+ if (!res.ok) {
135
+ const body = await res.json().catch(() => null);
136
+ const message = (body && typeof body === 'object' && 'error' in body && typeof body.error === 'string' ? body.error : null) ??
137
+ `Request failed: ${res.status} ${res.statusText}`;
138
+ throw new Error(message);
139
+ }
140
+ return res;
141
+ }
142
+ // -----------------------------------------------------------------------
143
+ // RAG Documents
144
+ // -----------------------------------------------------------------------
145
+ /** Upload and process a document */
146
+ async uploadDocument(fileName, base64Content, uploadedBy) {
147
+ let res;
148
+ try {
149
+ res = await fetch(`${this.baseUrl}/ai/documents`, {
150
+ method: 'POST',
151
+ headers: {
152
+ 'x-api-key': this.apiKey,
153
+ 'Content-Type': 'application/json',
154
+ },
155
+ body: JSON.stringify({ fileName, base64Content, uploadedBy }),
156
+ });
157
+ }
158
+ catch {
159
+ throw new Error(`APIHub unavailable: ${this.baseUrl}/ai/documents`);
160
+ }
161
+ if (!res.ok) {
162
+ const body = await res.json().catch(() => null);
163
+ const message = (body && typeof body === 'object' && 'error' in body && typeof body.error === 'string' ? body.error : null) ??
164
+ `Request failed: ${res.status} ${res.statusText}`;
165
+ throw new Error(message);
166
+ }
167
+ return res.json();
168
+ }
169
+ /** List all documents in the knowledge base */
170
+ async listDocuments() {
171
+ return this.cachedRequest('/ai/documents');
172
+ }
173
+ /** Delete a document and its chunks */
174
+ async deleteDocument(id) {
175
+ let res;
176
+ try {
177
+ res = await fetch(`${this.baseUrl}/ai/documents/${id}`, {
178
+ method: 'DELETE',
179
+ headers: { 'x-api-key': this.apiKey },
180
+ });
181
+ }
182
+ catch {
183
+ throw new Error(`APIHub unavailable: ${this.baseUrl}/ai/documents/${id}`);
184
+ }
185
+ if (!res.ok) {
186
+ const body = await res.json().catch(() => null);
187
+ const message = (body && typeof body === 'object' && 'error' in body && typeof body.error === 'string' ? body.error : null) ??
188
+ `Request failed: ${res.status} ${res.statusText}`;
189
+ throw new Error(message);
190
+ }
191
+ }
192
+ /** Search the knowledge base with a natural language query */
193
+ async searchDocuments(query, topK) {
194
+ let res;
195
+ try {
196
+ res = await fetch(`${this.baseUrl}/ai/search`, {
197
+ method: 'POST',
198
+ headers: {
199
+ 'x-api-key': this.apiKey,
200
+ 'Content-Type': 'application/json',
201
+ },
202
+ body: JSON.stringify({ query, topK }),
203
+ });
204
+ }
205
+ catch {
206
+ throw new Error(`APIHub unavailable: ${this.baseUrl}/ai/search`);
207
+ }
208
+ if (!res.ok) {
209
+ const body = await res.json().catch(() => null);
210
+ const message = (body && typeof body === 'object' && 'error' in body && typeof body.error === 'string' ? body.error : null) ??
211
+ `Request failed: ${res.status} ${res.statusText}`;
212
+ throw new Error(message);
213
+ }
214
+ return res.json();
215
+ }
216
+ // -----------------------------------------------------------------------
90
217
  // Business Units
91
218
  // -----------------------------------------------------------------------
92
219
  async getBusinessUnits() {
93
- return this.request('/business-units');
220
+ return this.cachedRequest('/business-units');
94
221
  }
95
222
  async getBusinessUnitsDropdown() {
96
- return this.request('/business-units/dropdown');
223
+ return this.cachedRequest('/business-units/dropdown');
97
224
  }
98
225
  async getBusinessUnit(id) {
99
226
  return this.request(`/business-units/${encodeURIComponent(id)}`);
@@ -102,30 +229,30 @@ export class ApiHubClient {
102
229
  // Cost Codes
103
230
  // -----------------------------------------------------------------------
104
231
  async getCostcodes() {
105
- return this.request('/costcodes');
232
+ return this.cachedRequest('/costcodes');
106
233
  }
107
234
  async getCostcodesDropdown() {
108
- return this.request('/costcodes/dropdown');
235
+ return this.cachedRequest('/costcodes/dropdown');
109
236
  }
110
237
  async getCostcode(id) {
111
238
  return this.request(`/costcodes/${encodeURIComponent(id)}`);
112
239
  }
113
240
  /** Get cost codes for a specific business unit, formatted for dropdown controls */
114
241
  async getCostcodesDropdownByBu(businessUnitId) {
115
- return this.request(`/costcodes/by-bu/${encodeURIComponent(businessUnitId)}/dropdown`);
242
+ return this.cachedRequest(`/costcodes/by-bu/${encodeURIComponent(businessUnitId)}/dropdown`);
116
243
  }
117
244
  /** Get cost codes for a specific business unit and pay type, formatted for dropdown controls */
118
245
  async getCostcodesDropdownByBuAndPayType(businessUnitId, payTypeCode) {
119
- return this.request(`/costcodes/by-bu/${encodeURIComponent(businessUnitId)}/by-paytype/${encodeURIComponent(payTypeCode)}/dropdown`);
246
+ return this.cachedRequest(`/costcodes/by-bu/${encodeURIComponent(businessUnitId)}/by-paytype/${encodeURIComponent(payTypeCode)}/dropdown`);
120
247
  }
121
248
  // -----------------------------------------------------------------------
122
249
  // Pay Types
123
250
  // -----------------------------------------------------------------------
124
251
  async getPaytypes() {
125
- return this.request('/paytypes');
252
+ return this.cachedRequest('/paytypes');
126
253
  }
127
254
  async getPaytypesDropdown() {
128
- return this.request('/paytypes/dropdown');
255
+ return this.cachedRequest('/paytypes/dropdown');
129
256
  }
130
257
  async getPaytype(id) {
131
258
  return this.request(`/paytypes/${encodeURIComponent(id)}`);
@@ -134,14 +261,14 @@ export class ApiHubClient {
134
261
  // Work Orders
135
262
  // -----------------------------------------------------------------------
136
263
  async getWorkorders() {
137
- return this.request('/workorders');
264
+ return this.cachedRequest('/workorders');
138
265
  }
139
266
  async getWorkordersDropdown() {
140
- return this.request('/workorders/dropdown');
267
+ return this.cachedRequest('/workorders/dropdown');
141
268
  }
142
269
  /** Get work orders for a specific business unit, formatted for dropdown controls */
143
270
  async getWorkordersDropdownByBu(businessUnitId) {
144
- return this.request(`/workorders/by-bu/${encodeURIComponent(businessUnitId)}/dropdown`);
271
+ return this.cachedRequest(`/workorders/by-bu/${encodeURIComponent(businessUnitId)}/dropdown`);
145
272
  }
146
273
  async getWorkorder(id) {
147
274
  return this.request(`/workorders/${encodeURIComponent(id)}`);
@@ -150,10 +277,10 @@ export class ApiHubClient {
150
277
  // Job Type / Job Steps
151
278
  // -----------------------------------------------------------------------
152
279
  async getJobtypejobsteps() {
153
- return this.request('/jobtypejobsteps');
280
+ return this.cachedRequest('/jobtypejobsteps');
154
281
  }
155
282
  async getJobtypejobstepsDropdown() {
156
- return this.request('/jobtypejobsteps/dropdown');
283
+ return this.cachedRequest('/jobtypejobsteps/dropdown');
157
284
  }
158
285
  async getJobtypejobstep(id) {
159
286
  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.13.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",