@brownandroot/api 1.1.0 → 1.2.1

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 CHANGED
@@ -10,9 +10,11 @@ npm install @brownandroot/api
10
10
 
11
11
  ---
12
12
 
13
- ## SvelteKit Remote Functions
13
+ ## Client-Side Cache API (recommended for SvelteKit)
14
14
 
15
- For SvelteKit apps, the package ships ready-to-use remote functions that run on the server and read credentials from environment variables automatically no manual client instantiation needed.
15
+ The package ships a browser-side API at `@brownandroot/api/cache` that wraps every remote function with an **IndexedDB stale-while-revalidate cache**. On the first call the data is fetched from the server; on every subsequent call the cached data is returned instantly. A background refresh runs automatically when the cached data is older than **4 hours**.
16
+
17
+ All list and dropdown functions accept an optional filter object — every entity field is available as an optional filter key. The `q` field performs a case-insensitive partial-text match across the entity's searchable text fields.
16
18
 
17
19
  ### Setup
18
20
 
@@ -27,21 +29,225 @@ Enable remote functions in `svelte.config.js` (if not already):
27
29
 
28
30
  ```js
29
31
  kit: {
30
- experimental: { remoteFunctions: true }
32
+ experimental: {
33
+ remoteFunctions: true
34
+ }
31
35
  }
32
36
  ```
33
37
 
34
38
  ### Usage
35
39
 
36
- Import directly from the domain sub-path and call in components:
40
+ ```svelte
41
+ <script lang="ts">
42
+ import { getWorkordersDropdown, getEmployees, clearCache } from '@brownandroot/api/cache'
43
+
44
+ // First page load: fetches from server and caches in IndexedDB.
45
+ // Subsequent loads: returns from IndexedDB instantly, refreshes in background.
46
+ const workorders = $derived(await getWorkordersDropdown({ businessUnitId: 'BU001' }))
47
+
48
+ // Multiple filters compose freely
49
+ const employees = $derived(await getEmployees({
50
+ hbu: 'TX01',
51
+ payClass: 'H',
52
+ q: 'john',
53
+ }))
54
+
55
+ // After a mutation, clear the affected entity so the next read re-fetches
56
+ async function onCreate() {
57
+ await createWorkorder(formData)
58
+ clearCache('workorders')
59
+ }
60
+ </script>
61
+ ```
62
+
63
+ ### Filter reference
64
+
65
+ All filter fields are optional. Omit the filter object entirely to return all records.
66
+
67
+ #### Workorders
68
+
69
+ ```typescript
70
+ import { getWorkorders, getWorkordersDropdown } from '@brownandroot/api/cache'
71
+
72
+ interface WorkorderFilters {
73
+ businessUnitId?: string // exact match
74
+ costCodeId?: string // exact match
75
+ isActive?: boolean // exact match
76
+ completed?: boolean // exact match
77
+ area?: string // exact match
78
+ parentWorkOrder?: string // exact match
79
+ q?: string // partial match on description or clientWorkOrderId
80
+ }
81
+
82
+ // Returns Workorder[]
83
+ const all = await getWorkorders()
84
+ const active = await getWorkorders({ isActive: true })
85
+ const byBu = await getWorkorders({ businessUnitId: 'BU001', isActive: true })
86
+ const search = await getWorkorders({ q: 'pipe' })
87
+
88
+ // Returns { value, label }[] — always applies isActive: true by default
89
+ const dropdown = await getWorkordersDropdown({ businessUnitId: 'BU001' })
90
+ ```
91
+
92
+ #### Employees
93
+
94
+ ```typescript
95
+ import { getEmployees, getEmployeesDropdown } from '@brownandroot/api/cache'
96
+
97
+ interface EmployeeFilters {
98
+ businessUnitId?: string // exact match
99
+ hbu?: string // exact match on home business unit
100
+ supervisor?: string // exact match on supervisor employee ID
101
+ payClass?: string // exact match
102
+ jobType?: string // exact match
103
+ jobStep?: string // exact match
104
+ sector?: string // exact match
105
+ division?: string // exact match
106
+ q?: string // partial match on name or email
107
+ }
108
+
109
+ const byHbu = await getEmployees({ hbu: 'TX01' })
110
+ const byName = await getEmployees({ q: 'john' })
111
+ const dropdown = await getEmployeesDropdown({ businessUnitId: 'BU001' })
112
+ ```
113
+
114
+ #### Cost Codes
115
+
116
+ ```typescript
117
+ import { getCostcodes, getCostcodesDropdown } from '@brownandroot/api/cache'
118
+
119
+ interface CostcodeFilters {
120
+ businessUnitId?: string // exact match
121
+ isActive?: boolean // exact match
122
+ entryFlag?: boolean // exact match
123
+ payTypeCode?: string // resolves pay type → objectAccount, filters by match
124
+ q?: string // partial match on description or jdeCostCode
125
+ }
126
+
127
+ const byBu = await getCostcodes({ businessUnitId: 'BU001' })
128
+ const search = await getCostcodes({ q: 'labor' })
129
+
130
+ // Dropdown applies isActive: true and entryFlag: true by default,
131
+ // and also excludes expired cost codes
132
+ const dropdown = await getCostcodesDropdown({ businessUnitId: 'BU001' })
133
+ const dropdownByPayType = await getCostcodesDropdown({
134
+ businessUnitId: 'BU001',
135
+ payTypeCode: 'PT01',
136
+ })
137
+ ```
138
+
139
+ > **payTypeCode cross-reference:** When `payTypeCode` is set, the filter looks up that pay type's `objectAccount` from the (also cached) pay types list and only returns cost codes whose `objectAccount` matches. If the pay type has no `objectAccount`, all cost codes pass this filter.
140
+
141
+ #### Pay Types
142
+
143
+ ```typescript
144
+ import { getPaytypes, getPaytypesDropdown } from '@brownandroot/api/cache'
145
+
146
+ interface PaytypeFilters {
147
+ payClass?: string // exact match
148
+ category?: string // exact match
149
+ type?: string // exact match
150
+ isActive?: boolean // exact match
151
+ perDiemPayType?: boolean // exact match
152
+ q?: string // partial match on description
153
+ }
154
+
155
+ const hourly = await getPaytypes({ payClass: 'H' })
156
+ const dropdown = await getPaytypesDropdown({ isActive: true })
157
+ // Dropdown returns { value, label, payClass }[]
158
+ ```
159
+
160
+ #### Business Units
161
+
162
+ ```typescript
163
+ import { getBusinessUnits, getBusinessUnitsDropdown } from '@brownandroot/api/cache'
164
+
165
+ interface BusinessUnitFilters {
166
+ subsector?: string // exact match
167
+ isActive?: boolean // exact match
168
+ clientId?: string // exact match
169
+ q?: string // partial match on description or clientDescription
170
+ }
171
+
172
+ const active = await getBusinessUnits({ isActive: true })
173
+ const dropdown = await getBusinessUnitsDropdown({ subsector: 'Gulf Coast' })
174
+ ```
175
+
176
+ #### Job Type / Job Steps
177
+
178
+ ```typescript
179
+ import { getJobtypejobsteps, getJobtypejobstepsDropdown } from '@brownandroot/api/cache'
180
+
181
+ interface JobtypejobstepFilters {
182
+ jobType?: string // exact match
183
+ payclass?: string // exact match
184
+ isActive?: boolean // exact match
185
+ grp?: string // exact match
186
+ q?: string // partial match on description, jobType, or jobStep
187
+ }
188
+
189
+ const welders = await getJobtypejobsteps({ jobType: 'WE' })
190
+ const dropdown = await getJobtypejobstepsDropdown({ isActive: true })
191
+ ```
192
+
193
+ ### Single-record lookups
194
+
195
+ Single-record functions always fetch fresh from the server (no IndexedDB):
196
+
197
+ ```typescript
198
+ import {
199
+ getEmployee,
200
+ getSupervisorChain,
201
+ getJdeFromEmail,
202
+ verifyIdentity,
203
+ getWorkorder,
204
+ getCostcode,
205
+ getPaytype,
206
+ getBusinessUnit,
207
+ getJobtypejobstep,
208
+ } from '@brownandroot/api/cache'
209
+
210
+ const emp = await getEmployee('12345')
211
+ const chain = await getSupervisorChain('12345')
212
+ const { jde, employee } = await getJdeFromEmail('john@example.com')
213
+ const verified = await verifyIdentity({
214
+ first3FirstName: 'joh',
215
+ first3LastName: 'doe',
216
+ dob: '1985-03-15',
217
+ ssn4: '4321',
218
+ })
219
+ ```
220
+
221
+ ### Cache management
222
+
223
+ ```typescript
224
+ import { clearCache } from '@brownandroot/api/cache'
225
+
226
+ clearCache() // clear all entities
227
+ clearCache('workorders') // clear a specific entity
228
+ clearCache('employees')
229
+ clearCache('costcodes')
230
+ clearCache('paytypes')
231
+ clearCache('businessUnits')
232
+ clearCache('jobtypejobsteps')
233
+ ```
234
+
235
+ Call `clearCache(entity)` immediately after any mutation that changes that entity so the next read fetches fresh data.
236
+
237
+ ---
238
+
239
+ ## Remote Functions (server-side)
240
+
241
+ For SvelteKit apps that need direct server-side access without the IndexedDB layer, the package also ships ready-to-use remote functions that run on the server and read credentials from environment variables automatically.
37
242
 
38
243
  ```svelte
39
244
  <script lang="ts">
40
- import { getEmployeesDropdown } from '@brownandroot/api/employees'
41
- import { getBusinessUnitsDropdown } from '@brownandroot/api/businessUnits'
245
+ import { getEmployees } from '@brownandroot/api/employees'
246
+ import { getBusinessUnits } from '@brownandroot/api/businessUnits'
42
247
 
43
- const employees = $derived(await getEmployeesDropdown())
44
- const businessUnits = $derived(await getBusinessUnitsDropdown())
248
+ // These always fetch from the server on every call
249
+ const employees = $derived(await getEmployees())
250
+ const businessUnits = $derived(await getBusinessUnits())
45
251
  </script>
46
252
  ```
47
253
 
@@ -52,7 +258,7 @@ import { query } from '$app/server'
52
258
  import { getSupervisorChain } from '@brownandroot/api/employees'
53
259
 
54
260
  export const getMyManagers = query(async () => {
55
- return (await getSupervisorChain('12345')).slice(0, 2)
261
+ return (await getSupervisorChain('12345')).slice(0, 2)
56
262
  })
57
263
  ```
58
264
 
@@ -85,36 +291,15 @@ const client = new ApiHubClient({
85
291
  ### Fetching
86
292
 
87
293
  ```typescript
88
- // All employees
89
294
  const employees = await client.getEmployees()
90
-
91
- // Dropdown format: { value: employeeId, label: name }[]
92
- const dropdown = await client.getEmployeesDropdown()
93
-
94
- // Single employee by ID — throws if not found
95
- const employee = await client.getEmployee('12345')
96
- ```
97
-
98
- ### Search
99
-
100
- ```typescript
101
- // By name (case-insensitive partial match)
102
- const results = await client.searchByName('John')
103
-
104
- // By email (case-insensitive partial match)
105
- const results = await client.searchByEmail('john@example.com')
106
-
107
- // By home business unit (case-insensitive partial match)
108
- const results = await client.searchByHbu('TX01')
295
+ const dropdown = await client.getEmployeesDropdown() // { value: employeeId, label: name }[]
296
+ const employee = await client.getEmployee('12345') // throws if not found
297
+ const employeePrivileged = await client.getEmployeePrivileged('12345') // includes hourlyRate and annualSalary
109
298
  ```
110
299
 
111
300
  ### Org hierarchy
112
301
 
113
302
  ```typescript
114
- // All employees reporting directly to a supervisor
115
- const reports = await client.getBySupervisor('12345')
116
-
117
- // Full supervisor chain above an employee (excludes the employee themselves)
118
303
  const chain = await client.getSupervisorChain('12345')
119
304
  ```
120
305
 
@@ -128,21 +313,14 @@ const { jde, employee } = await client.getJdeFromEmail('john@example.com')
128
313
 
129
314
  ### Identity verification
130
315
 
131
- Verifies an employee's identity from name, date of birth, and last 4 of SSN alone — no employee ID needed. Returns the full employee record on a match.
132
-
133
316
  ```typescript
134
317
  const employee = await client.verifyIdentity({
135
- first3FirstName: 'joh', // first 3 chars of first name, case-insensitive
136
- first3LastName: 'doe', // first 3 chars of last name, case-insensitive
137
- dob: '1985-03-15', // YYYY-MM-DD
138
- ssn4: '4321', // last 4 digits of SSN
318
+ first3FirstName: 'joh',
319
+ first3LastName: 'doe',
320
+ dob: '1985-03-15',
321
+ ssn4: '4321',
139
322
  })
140
-
141
- if (employee) {
142
- // identity confirmed — full Employee object returned
143
- } else {
144
- // no employee matched these inputs
145
- }
323
+ // Employee on match, null on no match
146
324
  // Throws on 400 (missing fields) or 503 (not configured server-side)
147
325
  ```
148
326
 
@@ -150,75 +328,59 @@ if (employee) {
150
328
 
151
329
  ```typescript
152
330
  interface Employee {
153
- // Identity
154
- employeeId: string
155
- name: string | null
156
- email: string | null
157
- badgeNumber: string | null
158
- nccerNumber: string | null
159
-
160
- // Organization
161
- company: string | null // company code
162
- businessUnitId: string | null
163
- hbu: string | null // home business unit
164
- departmentCode: string | null
165
- division: string | null
166
- sector: string | null
167
- subsector: string | null
168
-
169
- // Contact
170
- phone: string | null
171
-
172
- // Employment
173
- employementStatus: string | null
174
- employeePayStatus: string | null
175
- recordType: string | null
176
-
177
- // Job
178
- jobType: string | null
179
- jobStep: string | null
180
- jobDescription: string | null
181
- workSchedule: string | null
182
- shift: string | null
183
-
184
- // Pay & compensation
185
- payClass: string | null
186
- payFrequency: string | null
187
- payCycleCode: string | null
188
- checkRouteCode: string | null
189
- hourlyRate: string | null // string — format as needed
190
- annualSalary: string | null // string — format as needed
191
-
192
- // Tax
193
- residentTaxArea: string | null
194
- workTaxArea: string | null
195
-
196
- // Benefits
197
- benefitGroup: string | null
198
-
199
- // PTO
200
- topFlexPtoDate: string | null // ISO timestamp
201
- clientPtoDate: string | null // ISO timestamp
202
-
203
- // Security & reporting
204
- securityLevel: string | null
205
- reportingLevel: string | null
206
-
207
- // Relationships
208
- supervisor: string | null // supervisor employee ID
209
- mentor: string | null
210
-
211
- // Dates
212
- hireDate: string | null
213
- termDate: string | null
214
- adjustedServiceDate: string | null
215
-
216
- // Metadata
217
- source: string | null
218
- createdAt: string | null
219
- updatedAtJulian: number | null
220
- updatedAt: string | null
331
+ employeeId: string
332
+ name: string | null
333
+ email: string | null
334
+ personalEmail: string | null
335
+ clientEmail: string | null
336
+ workEmail: string | null
337
+ badgeNumber: string | null
338
+ nccerNumber: string | null
339
+ company: string | null
340
+ businessUnitId: string | null
341
+ hbu: string | null
342
+ departmentCode: string | null
343
+ division: string | null
344
+ sector: string | null
345
+ subsector: string | null
346
+ phone: string | null
347
+ employementStatus: string | null
348
+ employeePayStatus: string | null
349
+ recordType: string | null
350
+ jobType: string | null
351
+ jobStep: string | null
352
+ jobDescription: string | null
353
+ workSchedule: string | null
354
+ shift: string | null
355
+ payClass: string | null
356
+ payFrequency: string | null
357
+ payCycleCode: string | null
358
+ checkRouteCode: string | null
359
+ residentTaxArea: string | null
360
+ workTaxArea: string | null
361
+ benefitGroup: string | null
362
+ topFlexPtoDate: string | null
363
+ clientPtoDate: string | null
364
+ securityLevel: string | null
365
+ reportingLevel: string | null
366
+ supervisor: string | null
367
+ mentor: string | null
368
+ hireDate: string | null
369
+ termDate: string | null
370
+ adjustedServiceDate: string | null
371
+ identityHash: string | null
372
+ source: string | null
373
+ createdAt: string | null
374
+ updatedAtJulian: number | null
375
+ updatedAt: string | null
221
376
  }
377
+
378
+ interface EmployeePrivileged extends Employee {
379
+ hourlyRate: string | null
380
+ annualSalary: string | null
381
+ }
382
+
383
+ getEmployees, getEmployee, employee searches, getJdeFromEmail, and verifyIdentity all return the public Employee shape (without compensation fields). Use getEmployeePrivileged when compensation fields are required.
222
384
  ```
223
385
 
224
386
  ---
@@ -229,9 +391,6 @@ interface Employee {
229
391
  const units = await client.getBusinessUnits()
230
392
  const dropdown = await client.getBusinessUnitsDropdown() // { value, label }[]
231
393
  const unit = await client.getBusinessUnit('BU001')
232
-
233
- // Search by description (case-insensitive partial match)
234
- const results = await client.searchBusinessUnits('west')
235
394
  ```
236
395
 
237
396
  ---
@@ -242,15 +401,6 @@ const results = await client.searchBusinessUnits('west')
242
401
  const codes = await client.getCostcodes()
243
402
  const dropdown = await client.getCostcodesDropdown() // { value, label }[]
244
403
  const code = await client.getCostcode('CC001')
245
-
246
- // Filtered by business unit
247
- const buDropdown = await client.getCostcodesDropdownByBu('BU001')
248
-
249
- // Filtered by business unit and pay type
250
- const buPtDropdown = await client.getCostcodesDropdownByBuAndPayType('BU001', 'PT01')
251
-
252
- // Search by description or JDE cost code (case-insensitive partial match)
253
- const results = await client.searchCostcodes('labor')
254
404
  ```
255
405
 
256
406
  ---
@@ -261,9 +411,6 @@ const results = await client.searchCostcodes('labor')
261
411
  const types = await client.getPaytypes()
262
412
  const dropdown = await client.getPaytypesDropdown() // { value, label, payClass }[]
263
413
  const type = await client.getPaytype('PT001')
264
-
265
- // Search by description (case-insensitive partial match)
266
- const results = await client.searchPaytypes('regular')
267
414
  ```
268
415
 
269
416
  ---
@@ -274,12 +421,6 @@ const results = await client.searchPaytypes('regular')
274
421
  const orders = await client.getWorkorders()
275
422
  const dropdown = await client.getWorkordersDropdown() // { value, label }[]
276
423
  const order = await client.getWorkorder('WO001')
277
-
278
- // Filtered by business unit
279
- const buDropdown = await client.getWorkordersDropdownByBu('BU001')
280
-
281
- // Search by description or client work order ID (case-insensitive partial match)
282
- const results = await client.searchWorkorders('pipe')
283
424
  ```
284
425
 
285
426
  ---
@@ -301,11 +442,11 @@ const item = await client.getJobtypejobstep('JTJS001')
301
442
  ```typescript
302
443
  const result = await client.chat({
303
444
  messages: [{ role: 'user', content: 'Summarize this document...' }],
304
- source: 'my-app', // your application name
305
- user: 'jane.doe', // user identifier for logging
306
- function: 'summarize', // optional label for logging
307
- temperature: 0.7, // optional, default 0.7
308
- maxTokens: 1000, // optional
445
+ source: 'my-app',
446
+ user: 'jane.doe',
447
+ function: 'summarize',
448
+ temperature: 0.7,
449
+ maxTokens: 1000,
309
450
  })
310
451
 
311
452
  console.log(result.message.content)
@@ -314,17 +455,13 @@ console.log(result.usage) // { tokensIn, tokensOut, totalTokens }
314
455
 
315
456
  ### Streaming chat
316
457
 
317
- Returns a raw SSE `Response` for you to proxy or consume directly.
318
-
319
458
  ```typescript
320
459
  const response = await client.chatStream({
321
460
  messages: [{ role: 'user', content: 'Hello' }],
322
461
  userContext: { name: 'Jane Doe', department: 'Engineering', roles: ['admin'] },
323
- useRag: true, // optional, search document knowledge base
324
- tools: ['...'], // optional
462
+ useRag: true,
325
463
  source: 'my-app',
326
464
  })
327
-
328
465
  // Proxy to the browser, or read the SSE stream directly
329
466
  ```
330
467
 
@@ -338,31 +475,20 @@ const logs = await client.getLlmLogs() // newest first
338
475
 
339
476
  ## Documents (RAG)
340
477
 
341
- Upload and search documents in the knowledge base.
342
-
343
478
  ```typescript
344
- // Upload a document (PDF or CSV)
345
479
  const doc = await client.uploadDocument('report.pdf', base64Content, 'jane.doe')
346
-
347
- // List all documents
348
480
  const docs = await client.listDocuments()
349
-
350
- // Search with a natural language query
351
481
  const results = await client.searchDocuments('overtime policy', 5)
352
482
  // results: { chunkId, content, fileName, documentType, score }[]
353
-
354
- // Delete a document and all its chunks
355
483
  await client.deleteDocument(doc.id)
356
484
  ```
357
485
 
358
486
  ---
359
487
 
360
- ## Cache management
361
-
362
- All GET methods cache responses client-side for `cacheTtl` milliseconds (default 5 minutes). To invalidate:
488
+ ## Cache management (ApiHubClient)
363
489
 
364
490
  ```typescript
365
- client.clearCache() // clear everything
491
+ client.clearCache() // clear everything
366
492
  client.clearCache('/employees') // clear a specific path
367
493
  ```
368
494
 
@@ -390,20 +516,30 @@ import type {
390
516
  DropdownOption,
391
517
  PaytypeDropdownOption,
392
518
  } from '@brownandroot/api'
519
+
520
+ // Filter interfaces (from cache module)
521
+ import type {
522
+ EmployeeFilters,
523
+ WorkorderFilters,
524
+ CostcodeFilters,
525
+ PaytypeFilters,
526
+ BusinessUnitFilters,
527
+ JobtypejobstepFilters,
528
+ } from '@brownandroot/api/cache'
393
529
  ```
394
530
 
395
531
  ---
396
532
 
397
533
  ## Error handling
398
534
 
399
- All methods throw an `Error` when the API returns a non-OK response. The error message contains the server's detail or the HTTP status.
535
+ All methods throw an `Error` when the API returns a non-OK response.
400
536
 
401
537
  ```typescript
402
538
  try {
403
- const emp = await client.getEmployee('99999')
539
+ const emp = await getEmployee('99999')
404
540
  } catch (err) {
405
541
  console.error(err.message) // "Employee not found"
406
542
  }
407
543
  ```
408
544
 
409
- `verifyIdentity` is the exception — it returns `null` when no employee matches the inputs, and only throws for request errors (400, 503, network failure).
545
+ `verifyIdentity` returns `null` when no employee matches the inputs, and only throws for request errors (400, 503, network failure).
@@ -1,4 +1,3 @@
1
1
  export declare const getBusinessUnits: import("@sveltejs/kit").RemoteQueryFunction<void, import("./index.js").BusinessUnit[]>;
2
2
  export declare const getBusinessUnitsDropdown: import("@sveltejs/kit").RemoteQueryFunction<void, import("./index.js").DropdownOption[]>;
3
3
  export declare const getBusinessUnit: import("@sveltejs/kit").RemoteQueryFunction<string, import("./index.js").BusinessUnit>;
4
- export declare const searchBusinessUnits: import("@sveltejs/kit").RemoteQueryFunction<string, import("./index.js").BusinessUnit[]>;
@@ -4,4 +4,3 @@ import { getClient } from './client.js';
4
4
  export const getBusinessUnits = query(async () => getClient().getBusinessUnits());
5
5
  export const getBusinessUnitsDropdown = query(async () => getClient().getBusinessUnitsDropdown());
6
6
  export const getBusinessUnit = query(z.string(), async (id) => getClient().getBusinessUnit(id));
7
- export const searchBusinessUnits = query(z.string(), async (q) => getClient().searchBusinessUnits(q));
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Client-side cache API for @brownandroot/api.
3
+ *
4
+ * Wraps remote functions with an IndexedDB stale-while-revalidate cache so
5
+ * pages load instantly on subsequent visits. Filters are applied in-memory
6
+ * against the full cached dataset — no per-field server endpoints needed.
7
+ *
8
+ * Import from: @brownandroot/api/cache
9
+ */
10
+ import type { Employee, EmployeePrivileged, BusinessUnit, Costcode, Paytype, Workorder, Jobtypejobstep, DropdownOption, PaytypeDropdownOption } from './index.js';
11
+ import { type IdbStore } from './idb.js';
12
+ export interface EmployeeFilters {
13
+ /** Exact match on businessUnitId */
14
+ businessUnitId?: string;
15
+ /** Exact match on home business unit (hbu) */
16
+ hbu?: string;
17
+ /** Exact match on supervisor employee ID */
18
+ supervisor?: string;
19
+ /** Exact match on payClass */
20
+ payClass?: string;
21
+ /** Exact match on jobType */
22
+ jobType?: string;
23
+ /** Exact match on jobStep */
24
+ jobStep?: string;
25
+ /** Exact match on sector */
26
+ sector?: string;
27
+ /** Exact match on division */
28
+ division?: string;
29
+ /** Case-insensitive partial match on name or email */
30
+ q?: string;
31
+ }
32
+ export interface WorkorderFilters {
33
+ /** Exact match on businessUnitId */
34
+ businessUnitId?: string;
35
+ /** Exact match on costCodeId */
36
+ costCodeId?: string;
37
+ /** Exact match on isActive */
38
+ isActive?: boolean;
39
+ /** Exact match on completed */
40
+ completed?: boolean;
41
+ /** Exact match on area */
42
+ area?: string;
43
+ /** Exact match on parentWorkOrder */
44
+ parentWorkOrder?: string;
45
+ /** Case-insensitive partial match on description or clientWorkOrderId */
46
+ q?: string;
47
+ }
48
+ export interface CostcodeFilters {
49
+ /** Exact match on businessUnitId */
50
+ businessUnitId?: string;
51
+ /** Exact match on isActive */
52
+ isActive?: boolean;
53
+ /** Exact match on entryFlag */
54
+ entryFlag?: boolean;
55
+ /**
56
+ * Filter by pay type — resolves the pay type's objectAccount and returns
57
+ * only cost codes whose objectAccount matches. If the pay type has no
58
+ * objectAccount, all cost codes pass this filter.
59
+ */
60
+ payTypeCode?: string;
61
+ /** Case-insensitive partial match on description or jdeCostCode */
62
+ q?: string;
63
+ }
64
+ export interface PaytypeFilters {
65
+ /** Exact match on payClass */
66
+ payClass?: string;
67
+ /** Exact match on category */
68
+ category?: string;
69
+ /** Exact match on type */
70
+ type?: string;
71
+ /** Exact match on isActive */
72
+ isActive?: boolean;
73
+ /** Exact match on perDiemPayType */
74
+ perDiemPayType?: boolean;
75
+ /** Case-insensitive partial match on description */
76
+ q?: string;
77
+ }
78
+ export interface BusinessUnitFilters {
79
+ /** Exact match on subsector */
80
+ subsector?: string;
81
+ /** Exact match on isActive */
82
+ isActive?: boolean;
83
+ /** Exact match on clientId */
84
+ clientId?: string;
85
+ /** Case-insensitive partial match on description or clientDescription */
86
+ q?: string;
87
+ }
88
+ export interface JobtypejobstepFilters {
89
+ /** Exact match on jobType */
90
+ jobType?: string;
91
+ /** Exact match on payclass */
92
+ payclass?: string;
93
+ /** Exact match on isActive */
94
+ isActive?: boolean;
95
+ /** Exact match on grp */
96
+ grp?: string;
97
+ /** Case-insensitive partial match on description, jobType, or jobStep */
98
+ q?: string;
99
+ }
100
+ export declare function getEmployees(filters?: EmployeeFilters): Promise<Employee[]>;
101
+ export declare function getEmployeesDropdown(filters?: EmployeeFilters): Promise<DropdownOption[]>;
102
+ /** Single employee by ID — always fetches fresh (no IndexedDB). */
103
+ export declare function getEmployee(employeeId: string): Promise<Employee>;
104
+ /** Single employee by ID including compensation fields — always fetches fresh (no IndexedDB). */
105
+ export declare function getEmployeePrivileged(employeeId: string): Promise<EmployeePrivileged>;
106
+ /** Full supervisor chain above an employee — always fetches fresh. */
107
+ export declare function getSupervisorChain(employeeId: string): Promise<Employee[]>;
108
+ /** Look up JDE number and employee from email — always fetches fresh. */
109
+ export declare function getJdeFromEmail(email: string): Promise<{
110
+ jde: string | null;
111
+ employee: Employee | null;
112
+ }>;
113
+ /** Verify employee identity — always fetches fresh. */
114
+ export declare function verifyIdentity(inputs: {
115
+ first3FirstName: string;
116
+ first3LastName: string;
117
+ dob: string;
118
+ ssn4: string;
119
+ }): Promise<Employee | null>;
120
+ export declare function getWorkorders(filters?: WorkorderFilters): Promise<Workorder[]>;
121
+ export declare function getWorkordersDropdown(filters?: WorkorderFilters): Promise<DropdownOption[]>;
122
+ /** Single workorder by ID — always fetches fresh (no IndexedDB). */
123
+ export declare function getWorkorder(id: string): Promise<Workorder>;
124
+ export declare function getCostcodes(filters?: CostcodeFilters): Promise<Costcode[]>;
125
+ export declare function getCostcodesDropdown(filters?: CostcodeFilters): Promise<DropdownOption[]>;
126
+ /** Single cost code by ID — always fetches fresh (no IndexedDB). */
127
+ export declare function getCostcode(id: string): Promise<Costcode>;
128
+ export declare function getPaytypes(filters?: PaytypeFilters): Promise<Paytype[]>;
129
+ export declare function getPaytypesDropdown(filters?: PaytypeFilters): Promise<PaytypeDropdownOption[]>;
130
+ /** Single pay type by ID — always fetches fresh (no IndexedDB). */
131
+ export declare function getPaytype(id: string): Promise<Paytype>;
132
+ export declare function getBusinessUnits(filters?: BusinessUnitFilters): Promise<BusinessUnit[]>;
133
+ export declare function getBusinessUnitsDropdown(filters?: BusinessUnitFilters): Promise<DropdownOption[]>;
134
+ /** Single business unit by ID — always fetches fresh (no IndexedDB). */
135
+ export declare function getBusinessUnit(id: string): Promise<BusinessUnit>;
136
+ export declare function getJobtypejobsteps(filters?: JobtypejobstepFilters): Promise<Jobtypejobstep[]>;
137
+ export declare function getJobtypejobstepsDropdown(filters?: JobtypejobstepFilters): Promise<DropdownOption[]>;
138
+ /** Single job type/job step by ID — always fetches fresh (no IndexedDB). */
139
+ export declare function getJobtypejobstep(id: string): Promise<Jobtypejobstep>;
140
+ /**
141
+ * Clear the IndexedDB cache for a specific entity, or all entities.
142
+ *
143
+ * Call after mutations to ensure the next read fetches fresh data from
144
+ * the server instead of returning stale cached results.
145
+ *
146
+ * @example
147
+ * await createWorkorder(data)
148
+ * clearCache('workorders') // next getWorkorders() will re-fetch
149
+ *
150
+ * clearCache() // clear everything
151
+ */
152
+ export declare function clearCache(entity?: IdbStore): void;
package/dist/cache.js ADDED
@@ -0,0 +1,328 @@
1
+ /**
2
+ * Client-side cache API for @brownandroot/api.
3
+ *
4
+ * Wraps remote functions with an IndexedDB stale-while-revalidate cache so
5
+ * pages load instantly on subsequent visits. Filters are applied in-memory
6
+ * against the full cached dataset — no per-field server endpoints needed.
7
+ *
8
+ * Import from: @brownandroot/api/cache
9
+ */
10
+ import { idbGet, idbSet, idbClear } from './idb.js';
11
+ // Remote functions — called as HTTP on the client, directly on the server (SSR)
12
+ import { getEmployees as _getEmployees, getEmployee as _getEmployee, getEmployeePrivileged as _getEmployeePrivileged, getJdeFromEmail as _getJdeFromEmail, getSupervisorChain as _getSupervisorChain, verifyIdentity as _verifyIdentity, } from './employees.remote.js';
13
+ import { getBusinessUnits as _getBusinessUnits, getBusinessUnit as _getBusinessUnit } from './businessUnits.remote.js';
14
+ import { getCostcodes as _getCostcodes, getCostcode as _getCostcode } from './costcodes.remote.js';
15
+ import { getPaytypes as _getPaytypes, getPaytype as _getPaytype } from './paytypes.remote.js';
16
+ import { getWorkorders as _getWorkorders, getWorkorder as _getWorkorder } from './workorders.remote.js';
17
+ import { getJobtypejobsteps as _getJobtypejobsteps, getJobtypejobstep as _getJobtypejobstep, } from './jobtypejobsteps.remote.js';
18
+ // ---------------------------------------------------------------------------
19
+ // Internal: stale-while-revalidate fetch helpers
20
+ // ---------------------------------------------------------------------------
21
+ /** How long cached data is considered fresh before a background refresh is triggered. */
22
+ const CACHE_TTL_MS = 4 * 60 * 60 * 1000; // 4 hours
23
+ async function loadCached(store, fetcher) {
24
+ const cached = await idbGet(store, 'all');
25
+ if (cached) {
26
+ // Only refresh in the background if the cached data is older than the TTL
27
+ if (Date.now() - cached.fetchedAt >= CACHE_TTL_MS) {
28
+ fetcher()
29
+ .then((fresh) => idbSet(store, 'all', fresh))
30
+ .catch(() => { });
31
+ }
32
+ return cached.data;
33
+ }
34
+ // Cache miss — must await
35
+ const data = await fetcher();
36
+ await idbSet(store, 'all', data);
37
+ return data;
38
+ }
39
+ // ---------------------------------------------------------------------------
40
+ // Internal: filter helpers
41
+ // ---------------------------------------------------------------------------
42
+ function applyEmployeeFilters(data, f) {
43
+ if (!f)
44
+ return data;
45
+ return data.filter((e) => {
46
+ if (f.businessUnitId !== undefined && e.businessUnitId !== f.businessUnitId)
47
+ return false;
48
+ if (f.hbu !== undefined && e.hbu !== f.hbu)
49
+ return false;
50
+ if (f.supervisor !== undefined && e.supervisor !== f.supervisor)
51
+ return false;
52
+ if (f.payClass !== undefined && e.payClass !== f.payClass)
53
+ return false;
54
+ if (f.jobType !== undefined && e.jobType !== f.jobType)
55
+ return false;
56
+ if (f.jobStep !== undefined && e.jobStep !== f.jobStep)
57
+ return false;
58
+ if (f.sector !== undefined && e.sector !== f.sector)
59
+ return false;
60
+ if (f.division !== undefined && e.division !== f.division)
61
+ return false;
62
+ if (f.q) {
63
+ const q = f.q.toLowerCase();
64
+ const name = e.name?.toLowerCase() ?? '';
65
+ const email = e.email?.toLowerCase() ?? '';
66
+ if (!name.includes(q) && !email.includes(q))
67
+ return false;
68
+ }
69
+ return true;
70
+ });
71
+ }
72
+ function applyWorkorderFilters(data, f) {
73
+ if (!f)
74
+ return data;
75
+ return data.filter((w) => {
76
+ if (f.businessUnitId !== undefined && w.businessUnitId !== f.businessUnitId)
77
+ return false;
78
+ if (f.costCodeId !== undefined && w.costCodeId !== f.costCodeId)
79
+ return false;
80
+ if (f.isActive !== undefined && w.isActive !== f.isActive)
81
+ return false;
82
+ if (f.completed !== undefined && w.completed !== f.completed)
83
+ return false;
84
+ if (f.area !== undefined && w.area !== f.area)
85
+ return false;
86
+ if (f.parentWorkOrder !== undefined && w.parentWorkOrder !== f.parentWorkOrder)
87
+ return false;
88
+ if (f.q) {
89
+ const q = f.q.toLowerCase();
90
+ const desc = w.description?.toLowerCase() ?? '';
91
+ const wo = w.clientWorkOrderId?.toLowerCase() ?? '';
92
+ if (!desc.includes(q) && !wo.includes(q))
93
+ return false;
94
+ }
95
+ return true;
96
+ });
97
+ }
98
+ function applyPaytypeFilters(data, f) {
99
+ if (!f)
100
+ return data;
101
+ return data.filter((p) => {
102
+ if (f.payClass !== undefined && p.payClass !== f.payClass)
103
+ return false;
104
+ if (f.category !== undefined && p.category !== f.category)
105
+ return false;
106
+ if (f.type !== undefined && p.type !== f.type)
107
+ return false;
108
+ if (f.isActive !== undefined && p.isActive !== f.isActive)
109
+ return false;
110
+ if (f.perDiemPayType !== undefined && p.perDiemPayType !== f.perDiemPayType)
111
+ return false;
112
+ if (f.q) {
113
+ const q = f.q.toLowerCase();
114
+ if (!p.description.toLowerCase().includes(q))
115
+ return false;
116
+ }
117
+ return true;
118
+ });
119
+ }
120
+ function applyBusinessUnitFilters(data, f) {
121
+ if (!f)
122
+ return data;
123
+ return data.filter((b) => {
124
+ if (f.subsector !== undefined && b.subsector !== f.subsector)
125
+ return false;
126
+ if (f.isActive !== undefined && b.isActive !== f.isActive)
127
+ return false;
128
+ if (f.clientId !== undefined && b.clientId !== f.clientId)
129
+ return false;
130
+ if (f.q) {
131
+ const q = f.q.toLowerCase();
132
+ const desc = b.description.toLowerCase();
133
+ const client = b.clientDescription?.toLowerCase() ?? '';
134
+ if (!desc.includes(q) && !client.includes(q))
135
+ return false;
136
+ }
137
+ return true;
138
+ });
139
+ }
140
+ function applyJobtypejobstepFilters(data, f) {
141
+ if (!f)
142
+ return data;
143
+ return data.filter((j) => {
144
+ if (f.jobType !== undefined && j.jobType !== f.jobType)
145
+ return false;
146
+ if (f.payclass !== undefined && j.payclass !== f.payclass)
147
+ return false;
148
+ if (f.isActive !== undefined && j.isActive !== f.isActive)
149
+ return false;
150
+ if (f.grp !== undefined && j.grp !== f.grp)
151
+ return false;
152
+ if (f.q) {
153
+ const q = f.q.toLowerCase();
154
+ const desc = j.description?.toLowerCase() ?? '';
155
+ const jt = j.jobType?.toLowerCase() ?? '';
156
+ const js = j.jobStep?.toLowerCase() ?? '';
157
+ if (!desc.includes(q) && !jt.includes(q) && !js.includes(q))
158
+ return false;
159
+ }
160
+ return true;
161
+ });
162
+ }
163
+ // Costcodes filter is async because payTypeCode requires a cross-reference
164
+ async function applyCostcodeFilters(data, f) {
165
+ if (!f)
166
+ return data;
167
+ // Resolve payTypeCode → objectAccount
168
+ let payTypeObjectAccount;
169
+ if (f.payTypeCode !== undefined) {
170
+ const allPaytypes = await loadCached('paytypes', () => _getPaytypes());
171
+ payTypeObjectAccount = allPaytypes.find((p) => p.id === f.payTypeCode)?.objectAccount;
172
+ }
173
+ return data.filter((c) => {
174
+ if (f.businessUnitId !== undefined && c.businessUnitId !== f.businessUnitId)
175
+ return false;
176
+ if (f.isActive !== undefined && c.isActive !== f.isActive)
177
+ return false;
178
+ if (f.entryFlag !== undefined && c.entryFlag !== f.entryFlag)
179
+ return false;
180
+ if (f.payTypeCode !== undefined) {
181
+ // If paytype has an objectAccount, cost code must match it
182
+ if (payTypeObjectAccount && c.objectAccount !== payTypeObjectAccount)
183
+ return false;
184
+ }
185
+ if (f.q) {
186
+ const q = f.q.toLowerCase();
187
+ const desc = c.description?.toLowerCase() ?? '';
188
+ const code = c.jdeCostCode?.toLowerCase() ?? '';
189
+ if (!desc.includes(q) && !code.includes(q))
190
+ return false;
191
+ }
192
+ return true;
193
+ });
194
+ }
195
+ // ---------------------------------------------------------------------------
196
+ // Public API — Employees
197
+ // ---------------------------------------------------------------------------
198
+ export async function getEmployees(filters) {
199
+ const all = await loadCached('employees', () => _getEmployees());
200
+ return applyEmployeeFilters(all, filters);
201
+ }
202
+ export async function getEmployeesDropdown(filters) {
203
+ const results = await getEmployees(filters);
204
+ return results.map((e) => ({ value: e.employeeId, label: e.name ?? e.employeeId }));
205
+ }
206
+ /** Single employee by ID — always fetches fresh (no IndexedDB). */
207
+ export async function getEmployee(employeeId) {
208
+ return _getEmployee(employeeId);
209
+ }
210
+ /** Single employee by ID including compensation fields — always fetches fresh (no IndexedDB). */
211
+ export async function getEmployeePrivileged(employeeId) {
212
+ return _getEmployeePrivileged(employeeId);
213
+ }
214
+ /** Full supervisor chain above an employee — always fetches fresh. */
215
+ export async function getSupervisorChain(employeeId) {
216
+ return _getSupervisorChain(employeeId);
217
+ }
218
+ /** Look up JDE number and employee from email — always fetches fresh. */
219
+ export async function getJdeFromEmail(email) {
220
+ return _getJdeFromEmail(email);
221
+ }
222
+ /** Verify employee identity — always fetches fresh. */
223
+ export async function verifyIdentity(inputs) {
224
+ return _verifyIdentity(inputs);
225
+ }
226
+ // ---------------------------------------------------------------------------
227
+ // Public API — Workorders
228
+ // ---------------------------------------------------------------------------
229
+ export async function getWorkorders(filters) {
230
+ const all = await loadCached('workorders', () => _getWorkorders());
231
+ return applyWorkorderFilters(all, filters);
232
+ }
233
+ export async function getWorkordersDropdown(filters) {
234
+ const results = await getWorkorders({ isActive: true, ...filters });
235
+ return results.map((w) => ({ value: w.id, label: w.description ?? w.id }));
236
+ }
237
+ /** Single workorder by ID — always fetches fresh (no IndexedDB). */
238
+ export async function getWorkorder(id) {
239
+ return _getWorkorder(id);
240
+ }
241
+ // ---------------------------------------------------------------------------
242
+ // Public API — Cost Codes
243
+ // ---------------------------------------------------------------------------
244
+ export async function getCostcodes(filters) {
245
+ const all = await loadCached('costcodes', () => _getCostcodes());
246
+ return applyCostcodeFilters(all, filters);
247
+ }
248
+ export async function getCostcodesDropdown(filters) {
249
+ const mergedFilters = { isActive: true, entryFlag: true, ...filters };
250
+ const results = await getCostcodes(mergedFilters);
251
+ const now = new Date();
252
+ return results
253
+ .filter((c) => !c.expirationDate || new Date(c.expirationDate) >= now)
254
+ .map((c) => ({
255
+ value: c.id,
256
+ label: `${c.jdeCostCode ?? c.id} - ${c.description ?? ''}`,
257
+ }));
258
+ }
259
+ /** Single cost code by ID — always fetches fresh (no IndexedDB). */
260
+ export async function getCostcode(id) {
261
+ return _getCostcode(id);
262
+ }
263
+ // ---------------------------------------------------------------------------
264
+ // Public API — Pay Types
265
+ // ---------------------------------------------------------------------------
266
+ export async function getPaytypes(filters) {
267
+ const all = await loadCached('paytypes', () => _getPaytypes());
268
+ return applyPaytypeFilters(all, filters);
269
+ }
270
+ export async function getPaytypesDropdown(filters) {
271
+ const results = await getPaytypes(filters);
272
+ return results.map((p) => ({ value: p.id, label: p.description, payClass: p.payClass }));
273
+ }
274
+ /** Single pay type by ID — always fetches fresh (no IndexedDB). */
275
+ export async function getPaytype(id) {
276
+ return _getPaytype(id);
277
+ }
278
+ // ---------------------------------------------------------------------------
279
+ // Public API — Business Units
280
+ // ---------------------------------------------------------------------------
281
+ export async function getBusinessUnits(filters) {
282
+ const all = await loadCached('businessUnits', () => _getBusinessUnits());
283
+ return applyBusinessUnitFilters(all, filters);
284
+ }
285
+ export async function getBusinessUnitsDropdown(filters) {
286
+ const results = await getBusinessUnits(filters);
287
+ return results.map((b) => ({ value: b.id, label: b.description }));
288
+ }
289
+ /** Single business unit by ID — always fetches fresh (no IndexedDB). */
290
+ export async function getBusinessUnit(id) {
291
+ return _getBusinessUnit(id);
292
+ }
293
+ // ---------------------------------------------------------------------------
294
+ // Public API — Job Type / Job Steps
295
+ // ---------------------------------------------------------------------------
296
+ export async function getJobtypejobsteps(filters) {
297
+ const all = await loadCached('jobtypejobsteps', () => _getJobtypejobsteps());
298
+ return applyJobtypejobstepFilters(all, filters);
299
+ }
300
+ export async function getJobtypejobstepsDropdown(filters) {
301
+ const results = await getJobtypejobsteps(filters);
302
+ return results.map((j) => ({
303
+ value: j.id,
304
+ label: j.description ?? `${j.jobType ?? ''} ${j.jobStep ?? ''}`.trim(),
305
+ }));
306
+ }
307
+ /** Single job type/job step by ID — always fetches fresh (no IndexedDB). */
308
+ export async function getJobtypejobstep(id) {
309
+ return _getJobtypejobstep(id);
310
+ }
311
+ // ---------------------------------------------------------------------------
312
+ // Cache management
313
+ // ---------------------------------------------------------------------------
314
+ /**
315
+ * Clear the IndexedDB cache for a specific entity, or all entities.
316
+ *
317
+ * Call after mutations to ensure the next read fetches fresh data from
318
+ * the server instead of returning stale cached results.
319
+ *
320
+ * @example
321
+ * await createWorkorder(data)
322
+ * clearCache('workorders') // next getWorkorders() will re-fetch
323
+ *
324
+ * clearCache() // clear everything
325
+ */
326
+ export function clearCache(entity) {
327
+ idbClear(entity).catch(() => { });
328
+ }
@@ -1,9 +1,3 @@
1
1
  export declare const getCostcodes: import("@sveltejs/kit").RemoteQueryFunction<void, import("./index.js").Costcode[]>;
2
2
  export declare const getCostcodesDropdown: import("@sveltejs/kit").RemoteQueryFunction<void, import("./index.js").DropdownOption[]>;
3
3
  export declare const getCostcode: import("@sveltejs/kit").RemoteQueryFunction<string, import("./index.js").Costcode>;
4
- export declare const getCostcodesDropdownByBu: import("@sveltejs/kit").RemoteQueryFunction<string, import("./index.js").DropdownOption[]>;
5
- export declare const getCostcodesDropdownByBuAndPayType: import("@sveltejs/kit").RemoteQueryFunction<{
6
- businessUnitId: string;
7
- payTypeCode: string;
8
- }, import("./index.js").DropdownOption[]>;
9
- export declare const searchCostcodes: import("@sveltejs/kit").RemoteQueryFunction<string, import("./index.js").Costcode[]>;
@@ -1,13 +1,6 @@
1
1
  import { query } from '$app/server';
2
2
  import { z } from 'zod';
3
3
  import { getClient } from './client.js';
4
- const buPayTypeSchema = z.object({
5
- businessUnitId: z.string(),
6
- payTypeCode: z.string(),
7
- });
8
4
  export const getCostcodes = query(async () => getClient().getCostcodes());
9
5
  export const getCostcodesDropdown = query(async () => getClient().getCostcodesDropdown());
10
6
  export const getCostcode = query(z.string(), async (id) => getClient().getCostcode(id));
11
- export const getCostcodesDropdownByBu = query(z.string(), async (businessUnitId) => getClient().getCostcodesDropdownByBu(businessUnitId));
12
- export const getCostcodesDropdownByBuAndPayType = query(buPayTypeSchema, async ({ businessUnitId, payTypeCode }) => getClient().getCostcodesDropdownByBuAndPayType(businessUnitId, payTypeCode));
13
- export const searchCostcodes = query(z.string(), async (q) => getClient().searchCostcodes(q));
@@ -1,15 +1,12 @@
1
1
  export declare const getEmployees: import("@sveltejs/kit").RemoteQueryFunction<void, import("./index.js").Employee[]>;
2
2
  export declare const getEmployeesDropdown: import("@sveltejs/kit").RemoteQueryFunction<void, import("./index.js").DropdownOption[]>;
3
3
  export declare const getEmployee: import("@sveltejs/kit").RemoteQueryFunction<string, import("./index.js").Employee>;
4
- export declare const searchByName: import("@sveltejs/kit").RemoteQueryFunction<string, import("./index.js").Employee[]>;
5
- export declare const getBySupervisor: import("@sveltejs/kit").RemoteQueryFunction<string, import("./index.js").Employee[]>;
6
- export declare const searchByEmail: import("@sveltejs/kit").RemoteQueryFunction<string, import("./index.js").Employee[]>;
4
+ export declare const getEmployeePrivileged: import("@sveltejs/kit").RemoteQueryFunction<string, import("./index.js").EmployeePrivileged>;
7
5
  export declare const getSupervisorChain: import("@sveltejs/kit").RemoteQueryFunction<string, import("./index.js").Employee[]>;
8
6
  export declare const getJdeFromEmail: import("@sveltejs/kit").RemoteQueryFunction<string, {
9
7
  jde: string | null;
10
8
  employee: import("./index.js").Employee | null;
11
9
  }>;
12
- export declare const searchByHbu: import("@sveltejs/kit").RemoteQueryFunction<string, import("./index.js").Employee[]>;
13
10
  export declare const verifyIdentity: import("@sveltejs/kit").RemoteCommand<{
14
11
  first3FirstName: string;
15
12
  first3LastName: string;
@@ -11,10 +11,7 @@ const verifyIdentitySchema = z.object({
11
11
  export const getEmployees = query(async () => getClient().getEmployees());
12
12
  export const getEmployeesDropdown = query(async () => getClient().getEmployeesDropdown());
13
13
  export const getEmployee = query(employeeIdSchema, async (employeeId) => getClient().getEmployee(employeeId));
14
- export const searchByName = query(z.string(), async (name) => getClient().searchByName(name));
15
- export const getBySupervisor = query(employeeIdSchema, async (supervisorId) => getClient().getBySupervisor(supervisorId));
16
- export const searchByEmail = query(z.string(), async (email) => getClient().searchByEmail(email));
14
+ export const getEmployeePrivileged = query(employeeIdSchema, async (employeeId) => getClient().getEmployeePrivileged(employeeId));
17
15
  export const getSupervisorChain = query(employeeIdSchema, async (employeeId) => getClient().getSupervisorChain(employeeId));
18
16
  export const getJdeFromEmail = query(z.string(), async (email) => getClient().getJdeFromEmail(email));
19
- export const searchByHbu = query(z.string(), async (hbu) => getClient().searchByHbu(hbu));
20
17
  export const verifyIdentity = command(verifyIdentitySchema, async (inputs) => getClient().verifyIdentity(inputs));
package/dist/idb.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ declare const STORES: readonly ["workorders", "employees", "costcodes", "paytypes", "businessUnits", "jobtypejobsteps"];
2
+ export type IdbStore = (typeof STORES)[number];
3
+ interface IdbEntry<T> {
4
+ data: T;
5
+ fetchedAt: number;
6
+ }
7
+ /** Get a cached entry. Returns null in SSR (no window) or on cache miss. */
8
+ export declare function idbGet<T>(store: IdbStore, key: string): Promise<IdbEntry<T> | null>;
9
+ /** Store a value with the current timestamp. */
10
+ export declare function idbSet<T>(store: IdbStore, key: string, data: T): Promise<void>;
11
+ /** Clear a specific store, or all stores when called with no argument. */
12
+ export declare function idbClear(store?: IdbStore): Promise<void>;
13
+ export {};
package/dist/idb.js ADDED
@@ -0,0 +1,79 @@
1
+ const DB_NAME = 'apihub-cache';
2
+ const DB_VERSION = 1;
3
+ const STORES = [
4
+ 'workorders',
5
+ 'employees',
6
+ 'costcodes',
7
+ 'paytypes',
8
+ 'businessUnits',
9
+ 'jobtypejobsteps',
10
+ ];
11
+ function openDb() {
12
+ return new Promise((resolve, reject) => {
13
+ const req = indexedDB.open(DB_NAME, DB_VERSION);
14
+ req.onupgradeneeded = () => {
15
+ const db = req.result;
16
+ for (const store of STORES) {
17
+ if (!db.objectStoreNames.contains(store)) {
18
+ db.createObjectStore(store);
19
+ }
20
+ }
21
+ };
22
+ req.onsuccess = () => resolve(req.result);
23
+ req.onerror = () => reject(req.error);
24
+ });
25
+ }
26
+ /** Get a cached entry. Returns null in SSR (no window) or on cache miss. */
27
+ export async function idbGet(store, key) {
28
+ if (typeof window === 'undefined')
29
+ return null;
30
+ try {
31
+ const db = await openDb();
32
+ return new Promise((resolve, reject) => {
33
+ const tx = db.transaction(store, 'readonly');
34
+ const req = tx.objectStore(store).get(key);
35
+ req.onsuccess = () => resolve(req.result ?? null);
36
+ req.onerror = () => reject(req.error);
37
+ });
38
+ }
39
+ catch {
40
+ return null;
41
+ }
42
+ }
43
+ /** Store a value with the current timestamp. */
44
+ export async function idbSet(store, key, data) {
45
+ if (typeof window === 'undefined')
46
+ return;
47
+ try {
48
+ const db = await openDb();
49
+ await new Promise((resolve, reject) => {
50
+ const tx = db.transaction(store, 'readwrite');
51
+ const req = tx.objectStore(store).put({ data, fetchedAt: Date.now() }, key);
52
+ req.onsuccess = () => resolve();
53
+ req.onerror = () => reject(req.error);
54
+ });
55
+ }
56
+ catch {
57
+ // Ignore write failures — cache is best-effort
58
+ }
59
+ }
60
+ /** Clear a specific store, or all stores when called with no argument. */
61
+ export async function idbClear(store) {
62
+ if (typeof window === 'undefined')
63
+ return;
64
+ try {
65
+ const db = await openDb();
66
+ const storeNames = store ? [store] : [...STORES];
67
+ await new Promise((resolve, reject) => {
68
+ const tx = db.transaction(storeNames, 'readwrite');
69
+ for (const s of storeNames) {
70
+ tx.objectStore(s).clear();
71
+ }
72
+ tx.oncomplete = () => resolve();
73
+ tx.onerror = () => reject(tx.error);
74
+ });
75
+ }
76
+ catch {
77
+ // Ignore failures
78
+ }
79
+ }
package/dist/index.d.ts CHANGED
@@ -24,8 +24,6 @@ export interface Employee {
24
24
  jobDescription: string | null;
25
25
  workSchedule: string | null;
26
26
  shift: string | null;
27
- hourlyRate: string | null;
28
- annualSalary: string | null;
29
27
  termDate: string | null;
30
28
  hireDate: string | null;
31
29
  topFlexPtoDate: string | null;
@@ -50,6 +48,10 @@ export interface Employee {
50
48
  updatedAtJulian: number | null;
51
49
  updatedAt: string | null;
52
50
  }
51
+ export interface EmployeePrivileged extends Employee {
52
+ hourlyRate: string | null;
53
+ annualSalary: string | null;
54
+ }
53
55
  export interface ApiHubClientOptions {
54
56
  baseUrl: string;
55
57
  apiKey: string;
@@ -218,6 +220,8 @@ export declare class ApiHubClient {
218
220
  getEmployeesDropdown(): Promise<DropdownOption[]>;
219
221
  /** Get a single employee by employeeId */
220
222
  getEmployee(employeeId: string): Promise<Employee>;
223
+ /** Get a single employee with compensation fields by employeeId */
224
+ getEmployeePrivileged(employeeId: string): Promise<EmployeePrivileged>;
221
225
  /** Search employees by name (case-insensitive partial match) */
222
226
  searchByName(name: string): Promise<Employee[]>;
223
227
  /** Get all employees reporting to a supervisor */
package/dist/index.js CHANGED
@@ -61,6 +61,10 @@ export class ApiHubClient {
61
61
  async getEmployee(employeeId) {
62
62
  return this.request(`/employees/${encodeURIComponent(employeeId)}`);
63
63
  }
64
+ /** Get a single employee with compensation fields by employeeId */
65
+ async getEmployeePrivileged(employeeId) {
66
+ return this.request(`/employees/privileged/${encodeURIComponent(employeeId)}`);
67
+ }
64
68
  /** Search employees by name (case-insensitive partial match) */
65
69
  async searchByName(name) {
66
70
  return this.request(`/employees/search?name=${encodeURIComponent(name)}`);
@@ -1,4 +1,3 @@
1
1
  export declare const getPaytypes: import("@sveltejs/kit").RemoteQueryFunction<void, import("./index.js").Paytype[]>;
2
2
  export declare const getPaytypesDropdown: import("@sveltejs/kit").RemoteQueryFunction<void, import("./index.js").PaytypeDropdownOption[]>;
3
3
  export declare const getPaytype: import("@sveltejs/kit").RemoteQueryFunction<string, import("./index.js").Paytype>;
4
- export declare const searchPaytypes: import("@sveltejs/kit").RemoteQueryFunction<string, import("./index.js").Paytype[]>;
@@ -4,4 +4,3 @@ import { getClient } from './client.js';
4
4
  export const getPaytypes = query(async () => getClient().getPaytypes());
5
5
  export const getPaytypesDropdown = query(async () => getClient().getPaytypesDropdown());
6
6
  export const getPaytype = query(z.string(), async (id) => getClient().getPaytype(id));
7
- export const searchPaytypes = query(z.string(), async (q) => getClient().searchPaytypes(q));
@@ -1,5 +1,3 @@
1
1
  export declare const getWorkorders: import("@sveltejs/kit").RemoteQueryFunction<void, import("./index.js").Workorder[]>;
2
2
  export declare const getWorkordersDropdown: import("@sveltejs/kit").RemoteQueryFunction<void, import("./index.js").DropdownOption[]>;
3
- export declare const getWorkordersDropdownByBu: import("@sveltejs/kit").RemoteQueryFunction<string, import("./index.js").DropdownOption[]>;
4
3
  export declare const getWorkorder: import("@sveltejs/kit").RemoteQueryFunction<string, import("./index.js").Workorder>;
5
- export declare const searchWorkorders: import("@sveltejs/kit").RemoteQueryFunction<string, import("./index.js").Workorder[]>;
@@ -3,6 +3,4 @@ import { z } from 'zod';
3
3
  import { getClient } from './client.js';
4
4
  export const getWorkorders = query(async () => getClient().getWorkorders());
5
5
  export const getWorkordersDropdown = query(async () => getClient().getWorkordersDropdown());
6
- export const getWorkordersDropdownByBu = query(z.string(), async (businessUnitId) => getClient().getWorkordersDropdownByBu(businessUnitId));
7
6
  export const getWorkorder = query(z.string(), async (id) => getClient().getWorkorder(id));
8
- export const searchWorkorders = query(z.string(), async (q) => getClient().searchWorkorders(q));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brownandroot/api",
3
- "version": "1.1.0",
3
+ "version": "1.2.1",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -14,6 +14,10 @@
14
14
  "types": "./dist/index.d.ts",
15
15
  "default": "./dist/index.js"
16
16
  },
17
+ "./cache": {
18
+ "types": "./dist/cache.d.ts",
19
+ "default": "./dist/cache.js"
20
+ },
17
21
  "./employees": {
18
22
  "types": "./dist/employees.remote.d.ts",
19
23
  "default": "./dist/employees.remote.js"