@brownandroot/api 1.2.0 → 2.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.js CHANGED
@@ -1,12 +1,67 @@
1
+ export class ApiHubError extends Error {
2
+ status;
3
+ path;
4
+ url;
5
+ responseBody;
6
+ retriable;
7
+ service;
8
+ constructor(options) {
9
+ super(options.message);
10
+ this.name = 'ApiHubError';
11
+ this.status = options.status;
12
+ this.path = options.path;
13
+ this.url = options.url;
14
+ this.responseBody = options.responseBody;
15
+ this.retriable = options.retriable;
16
+ this.service = options.service ?? 'apihub';
17
+ }
18
+ }
1
19
  export class ApiHubClient {
2
20
  baseUrl;
3
21
  apiKey;
4
22
  cacheTtl;
23
+ timeoutMs;
24
+ retryCount;
25
+ onError;
5
26
  cache = new Map();
6
27
  constructor(options) {
7
28
  this.baseUrl = options.baseUrl.replace(/\/+$/, '');
8
29
  this.apiKey = options.apiKey;
9
30
  this.cacheTtl = options.cacheTtl ?? 5 * 60 * 1000;
31
+ this.timeoutMs = options.timeoutMs ?? 10_000;
32
+ this.retryCount = options.retryCount ?? 2;
33
+ this.onError = options.onError;
34
+ }
35
+ isRetriableStatus(status) {
36
+ return status === 429 || status === 502 || status === 503 || status === 504;
37
+ }
38
+ createApiHubError(options) {
39
+ const error = new ApiHubError({ ...options, service: 'apihub' });
40
+ this.onError?.(error);
41
+ if (typeof window === 'undefined') {
42
+ console.error('[APIHubError]', {
43
+ status: error.status,
44
+ path: error.path,
45
+ url: error.url,
46
+ retriable: error.retriable,
47
+ responseBody: error.responseBody,
48
+ message: error.message,
49
+ });
50
+ }
51
+ return error;
52
+ }
53
+ async fetchWithTimeout(url, init) {
54
+ const controller = new AbortController();
55
+ const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
56
+ try {
57
+ return await fetch(url, { ...init, signal: controller.signal });
58
+ }
59
+ finally {
60
+ clearTimeout(timeout);
61
+ }
62
+ }
63
+ async delay(ms) {
64
+ await new Promise((resolve) => setTimeout(resolve, ms));
10
65
  }
11
66
  /** Clear all cached responses, or a specific path */
12
67
  clearCache(path) {
@@ -32,22 +87,62 @@ export class ApiHubClient {
32
87
  return data;
33
88
  }
34
89
  async request(path) {
35
- let res;
36
- try {
37
- res = await fetch(`${this.baseUrl}${path}`, {
38
- headers: { 'x-api-key': this.apiKey },
39
- });
40
- }
41
- catch {
42
- throw new Error(`APIHub unavailable: ${this.baseUrl}${path}`);
43
- }
44
- if (!res.ok) {
90
+ const url = `${this.baseUrl}${path}`;
91
+ for (let attempt = 0; attempt <= this.retryCount; attempt++) {
92
+ let res;
93
+ try {
94
+ res = await this.fetchWithTimeout(url, {
95
+ headers: { 'x-api-key': this.apiKey },
96
+ });
97
+ }
98
+ catch (error) {
99
+ const retriable = attempt < this.retryCount;
100
+ if (retriable) {
101
+ const jitter = Math.floor(Math.random() * 100);
102
+ await this.delay(150 * Math.pow(2, attempt) + jitter);
103
+ continue;
104
+ }
105
+ const message = error instanceof Error && error.name === 'AbortError'
106
+ ? `APIHub request timed out after ${this.timeoutMs}ms`
107
+ : 'APIHub unavailable';
108
+ throw this.createApiHubError({
109
+ message,
110
+ status: null,
111
+ path,
112
+ url,
113
+ responseBody: null,
114
+ retriable: false,
115
+ });
116
+ }
117
+ if (res.ok) {
118
+ return res.json();
119
+ }
45
120
  const body = await res.json().catch(() => null);
121
+ const retriable = this.isRetriableStatus(res.status);
122
+ if (retriable && attempt < this.retryCount) {
123
+ const jitter = Math.floor(Math.random() * 100);
124
+ await this.delay(150 * Math.pow(2, attempt) + jitter);
125
+ continue;
126
+ }
46
127
  const message = (body && typeof body === 'object' && 'error' in body && typeof body.error === 'string' ? body.error : null) ??
47
128
  `Request failed: ${res.status} ${res.statusText}`;
48
- throw new Error(message);
129
+ throw this.createApiHubError({
130
+ message,
131
+ status: res.status,
132
+ path,
133
+ url,
134
+ responseBody: body,
135
+ retriable,
136
+ });
49
137
  }
50
- return res.json();
138
+ throw this.createApiHubError({
139
+ message: 'APIHub request failed after retries',
140
+ status: null,
141
+ path,
142
+ url,
143
+ responseBody: null,
144
+ retriable: false,
145
+ });
51
146
  }
52
147
  /** Get all employees */
53
148
  async getEmployees() {
@@ -333,3 +428,6 @@ export class ApiHubClient {
333
428
  return this.request(`/jobtypejobsteps/${encodeURIComponent(id)}`);
334
429
  }
335
430
  }
431
+ export { employees, workorders, costcodes, paytypes, businessUnits, jobtypejobsteps, clearCache } from './cache.js';
432
+ export { llm } from './llm.remote.js';
433
+ export { rag } from './rag.remote.js';
@@ -1,3 +1,5 @@
1
- export declare const getJobtypejobsteps: import("@sveltejs/kit").RemoteQueryFunction<void, import("./index.js").Jobtypejobstep[]>;
2
- export declare const getJobtypejobstepsDropdown: import("@sveltejs/kit").RemoteQueryFunction<void, import("./index.js").DropdownOption[]>;
3
- export declare const getJobtypejobstep: import("@sveltejs/kit").RemoteQueryFunction<string, import("./index.js").Jobtypejobstep>;
1
+ export declare const jobtypejobsteps: {
2
+ getAll: import("@sveltejs/kit").RemoteQueryFunction<void, import("./index.js").Jobtypejobstep[]>;
3
+ dropdown: import("@sveltejs/kit").RemoteQueryFunction<void, import("./index.js").DropdownOption[]>;
4
+ get: import("@sveltejs/kit").RemoteQueryFunction<string, import("./index.js").Jobtypejobstep | null>;
5
+ };
@@ -1,6 +1,26 @@
1
1
  import { query } from '$app/server';
2
2
  import { z } from 'zod';
3
3
  import { getClient } from './client.js';
4
- export const getJobtypejobsteps = query(async () => getClient().getJobtypejobsteps());
5
- export const getJobtypejobstepsDropdown = query(async () => getClient().getJobtypejobstepsDropdown());
6
- export const getJobtypejobstep = query(z.string(), async (id) => getClient().getJobtypejobstep(id));
4
+ function isNotFoundError(error) {
5
+ if (!(error instanceof Error))
6
+ return false;
7
+ return /not found/i.test(error.message);
8
+ }
9
+ async function asNullable(fetcher) {
10
+ try {
11
+ return await fetcher();
12
+ }
13
+ catch (error) {
14
+ if (isNotFoundError(error))
15
+ return null;
16
+ throw error;
17
+ }
18
+ }
19
+ const getAll = query(async () => getClient().getJobtypejobsteps());
20
+ const dropdown = query(async () => getClient().getJobtypejobstepsDropdown());
21
+ const get = query(z.string(), async (id) => asNullable(() => getClient().getJobtypejobstep(id)));
22
+ export const jobtypejobsteps = {
23
+ getAll,
24
+ dropdown,
25
+ get,
26
+ };
@@ -1,12 +1,14 @@
1
- export declare const getLlmLogs: import("@sveltejs/kit").RemoteQueryFunction<void, import("./index.js").LlmLog[]>;
2
- export declare const chat: import("@sveltejs/kit").RemoteCommand<{
3
- user: string;
4
- messages: {
5
- role: "system" | "user" | "assistant";
6
- content: string;
7
- }[];
8
- source: string;
9
- function?: string | undefined;
10
- temperature?: number | undefined;
11
- maxTokens?: number | undefined;
12
- }, Promise<import("./index.js").ChatResponse>>;
1
+ export declare const llm: {
2
+ getLogs: import("@sveltejs/kit").RemoteQueryFunction<void, import("./index.js").LlmLog[]>;
3
+ chat: import("@sveltejs/kit").RemoteCommand<{
4
+ user: string;
5
+ messages: {
6
+ role: "system" | "user" | "assistant";
7
+ content: string;
8
+ }[];
9
+ source: string;
10
+ function?: string | undefined;
11
+ temperature?: number | undefined;
12
+ maxTokens?: number | undefined;
13
+ }, Promise<import("./index.js").ChatResponse>>;
14
+ };
@@ -13,5 +13,9 @@ const chatRequestSchema = z.object({
13
13
  temperature: z.number().min(0).max(2).optional(),
14
14
  maxTokens: z.number().int().positive().optional(),
15
15
  });
16
- export const getLlmLogs = query(async () => getClient().getLlmLogs());
17
- export const chat = command(chatRequestSchema, async (request) => getClient().chat(request));
16
+ const getLogs = query(async () => getClient().getLlmLogs());
17
+ const chat = command(chatRequestSchema, async (request) => getClient().chat(request));
18
+ export const llm = {
19
+ getLogs,
20
+ chat,
21
+ };
@@ -1,3 +1,5 @@
1
- export declare const getPaytypes: import("@sveltejs/kit").RemoteQueryFunction<void, import("./index.js").Paytype[]>;
2
- export declare const getPaytypesDropdown: import("@sveltejs/kit").RemoteQueryFunction<void, import("./index.js").PaytypeDropdownOption[]>;
3
- export declare const getPaytype: import("@sveltejs/kit").RemoteQueryFunction<string, import("./index.js").Paytype>;
1
+ export declare const paytypes: {
2
+ getAll: import("@sveltejs/kit").RemoteQueryFunction<void, import("./index.js").Paytype[]>;
3
+ dropdown: import("@sveltejs/kit").RemoteQueryFunction<void, import("./index.js").PaytypeDropdownOption[]>;
4
+ get: import("@sveltejs/kit").RemoteQueryFunction<string, import("./index.js").Paytype | null>;
5
+ };
@@ -1,6 +1,26 @@
1
1
  import { query } from '$app/server';
2
2
  import { z } from 'zod';
3
3
  import { getClient } from './client.js';
4
- export const getPaytypes = query(async () => getClient().getPaytypes());
5
- export const getPaytypesDropdown = query(async () => getClient().getPaytypesDropdown());
6
- export const getPaytype = query(z.string(), async (id) => getClient().getPaytype(id));
4
+ function isNotFoundError(error) {
5
+ if (!(error instanceof Error))
6
+ return false;
7
+ return /not found/i.test(error.message);
8
+ }
9
+ async function asNullable(fetcher) {
10
+ try {
11
+ return await fetcher();
12
+ }
13
+ catch (error) {
14
+ if (isNotFoundError(error))
15
+ return null;
16
+ throw error;
17
+ }
18
+ }
19
+ const getAll = query(async () => getClient().getPaytypes());
20
+ const dropdown = query(async () => getClient().getPaytypesDropdown());
21
+ const get = query(z.string(), async (id) => asNullable(() => getClient().getPaytype(id)));
22
+ export const paytypes = {
23
+ getAll,
24
+ dropdown,
25
+ get,
26
+ };
@@ -1,11 +1,13 @@
1
- export declare const listDocuments: import("@sveltejs/kit").RemoteQueryFunction<void, import("./index.js").DocumentRecord[]>;
2
- export declare const uploadDocument: import("@sveltejs/kit").RemoteCommand<{
3
- fileName: string;
4
- base64Content: string;
5
- uploadedBy: string;
6
- }, Promise<import("./index.js").DocumentRecord>>;
7
- export declare const deleteDocument: import("@sveltejs/kit").RemoteCommand<number, Promise<void>>;
8
- export declare const searchDocuments: import("@sveltejs/kit").RemoteCommand<{
9
- query: string;
10
- topK?: number | undefined;
11
- }, Promise<import("./index.js").SearchResult[]>>;
1
+ export declare const rag: {
2
+ list: import("@sveltejs/kit").RemoteQueryFunction<void, import("./index.js").DocumentRecord[]>;
3
+ upload: import("@sveltejs/kit").RemoteCommand<{
4
+ fileName: string;
5
+ base64Content: string;
6
+ uploadedBy: string;
7
+ }, Promise<import("./index.js").DocumentRecord>>;
8
+ delete: import("@sveltejs/kit").RemoteCommand<number, Promise<void>>;
9
+ search: import("@sveltejs/kit").RemoteCommand<{
10
+ query: string;
11
+ topK?: number | undefined;
12
+ }, Promise<import("./index.js").SearchResult[]>>;
13
+ };
@@ -10,7 +10,13 @@ const searchDocumentsSchema = z.object({
10
10
  query: z.string().min(1),
11
11
  topK: z.number().int().positive().optional(),
12
12
  });
13
- export const listDocuments = query(async () => getClient().listDocuments());
14
- export const uploadDocument = command(uploadDocumentSchema, async ({ fileName, base64Content, uploadedBy }) => getClient().uploadDocument(fileName, base64Content, uploadedBy));
15
- export const deleteDocument = command(z.number().int().positive(), async (id) => getClient().deleteDocument(id));
16
- export const searchDocuments = command(searchDocumentsSchema, async ({ query: q, topK }) => getClient().searchDocuments(q, topK));
13
+ const list = query(async () => getClient().listDocuments());
14
+ const upload = command(uploadDocumentSchema, async ({ fileName, base64Content, uploadedBy }) => getClient().uploadDocument(fileName, base64Content, uploadedBy));
15
+ const remove = command(z.number().int().positive(), async (id) => getClient().deleteDocument(id));
16
+ const search = command(searchDocumentsSchema, async ({ query: q, topK }) => getClient().searchDocuments(q, topK));
17
+ export const rag = {
18
+ list,
19
+ upload,
20
+ delete: remove,
21
+ search,
22
+ };
@@ -1,3 +1,5 @@
1
- export declare const getWorkorders: import("@sveltejs/kit").RemoteQueryFunction<void, import("./index.js").Workorder[]>;
2
- export declare const getWorkordersDropdown: import("@sveltejs/kit").RemoteQueryFunction<void, import("./index.js").DropdownOption[]>;
3
- export declare const getWorkorder: import("@sveltejs/kit").RemoteQueryFunction<string, import("./index.js").Workorder>;
1
+ export declare const workorders: {
2
+ getAll: import("@sveltejs/kit").RemoteQueryFunction<void, import("./index.js").Workorder[]>;
3
+ dropdown: import("@sveltejs/kit").RemoteQueryFunction<void, import("./index.js").DropdownOption[]>;
4
+ get: import("@sveltejs/kit").RemoteQueryFunction<string, import("./index.js").Workorder | null>;
5
+ };
@@ -1,6 +1,26 @@
1
1
  import { query } from '$app/server';
2
2
  import { z } from 'zod';
3
3
  import { getClient } from './client.js';
4
- export const getWorkorders = query(async () => getClient().getWorkorders());
5
- export const getWorkordersDropdown = query(async () => getClient().getWorkordersDropdown());
6
- export const getWorkorder = query(z.string(), async (id) => getClient().getWorkorder(id));
4
+ function isNotFoundError(error) {
5
+ if (!(error instanceof Error))
6
+ return false;
7
+ return /not found/i.test(error.message);
8
+ }
9
+ async function asNullable(fetcher) {
10
+ try {
11
+ return await fetcher();
12
+ }
13
+ catch (error) {
14
+ if (isNotFoundError(error))
15
+ return null;
16
+ throw error;
17
+ }
18
+ }
19
+ const getAll = query(async () => getClient().getWorkorders());
20
+ const dropdown = query(async () => getClient().getWorkordersDropdown());
21
+ const get = query(z.string(), async (id) => asNullable(() => getClient().getWorkorder(id)));
22
+ export const workorders = {
23
+ getAll,
24
+ dropdown,
25
+ get,
26
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brownandroot/api",
3
- "version": "1.2.0",
3
+ "version": "2.0.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -13,42 +13,6 @@
13
13
  ".": {
14
14
  "types": "./dist/index.d.ts",
15
15
  "default": "./dist/index.js"
16
- },
17
- "./cache": {
18
- "types": "./dist/cache.d.ts",
19
- "default": "./dist/cache.js"
20
- },
21
- "./employees": {
22
- "types": "./dist/employees.remote.d.ts",
23
- "default": "./dist/employees.remote.js"
24
- },
25
- "./businessUnits": {
26
- "types": "./dist/businessUnits.remote.d.ts",
27
- "default": "./dist/businessUnits.remote.js"
28
- },
29
- "./costcodes": {
30
- "types": "./dist/costcodes.remote.d.ts",
31
- "default": "./dist/costcodes.remote.js"
32
- },
33
- "./paytypes": {
34
- "types": "./dist/paytypes.remote.d.ts",
35
- "default": "./dist/paytypes.remote.js"
36
- },
37
- "./workorders": {
38
- "types": "./dist/workorders.remote.d.ts",
39
- "default": "./dist/workorders.remote.js"
40
- },
41
- "./jobtypejobsteps": {
42
- "types": "./dist/jobtypejobsteps.remote.d.ts",
43
- "default": "./dist/jobtypejobsteps.remote.js"
44
- },
45
- "./llm": {
46
- "types": "./dist/llm.remote.d.ts",
47
- "default": "./dist/llm.remote.js"
48
- },
49
- "./rag": {
50
- "types": "./dist/rag.remote.d.ts",
51
- "default": "./dist/rag.remote.js"
52
16
  }
53
17
  },
54
18
  "files": [