@markwharton/eh-payroll 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/README.md CHANGED
@@ -38,6 +38,12 @@ const { rosterShifts: published } = await client.getRosterShifts('2026-02-03', '
38
38
  selectAllRoles: true
39
39
  });
40
40
 
41
+ // Get business locations
42
+ const { locations } = await client.getLocations();
43
+
44
+ // List kiosks
45
+ const { kiosks } = await client.getKiosks();
46
+
41
47
  // Get kiosk staff (time and attendance)
42
48
  const { staff } = await client.getKioskStaff(kioskId);
43
49
 
@@ -66,8 +72,10 @@ All methods return `{ data?, error? }` result objects rather than throwing excep
66
72
  | `getEmployees(options?)` | `EHEmployeeOptions?` | `{ employees?, error? }` |
67
73
  | `getEmployee(employeeId)` | `number` | `{ employee?, error? }` |
68
74
  | `getStandardHours(employeeId)` | `number` | `{ standardHours?, error? }` |
75
+ | `getLocations()` | — | `{ locations?, error? }` |
69
76
  | `getEmployeeGroups()` | — | `{ employeeGroups?, error? }` |
70
77
  | `getRosterShifts(from, to, options?)` | `string, string, EHRosterShiftOptions?` | `{ rosterShifts?, error? }` |
78
+ | `getKiosks()` | — | `{ kiosks?, error? }` |
71
79
  | `getKioskStaff(kioskId, options?)` | `number, EHKioskStaffOptions?` | `{ staff?, error? }` |
72
80
  | `getReportFields()` | — | `{ fields?, error? }` |
73
81
  | `getEmployeeDetailsReport(options?)` | `EHEmployeeDetailsReportOptions?` | `{ records?, error? }` |
@@ -104,9 +112,12 @@ const client = new EHClient({
104
112
  | Cache Key | Default TTL |
105
113
  |-----------|-------------|
106
114
  | Employees | 5 min |
115
+ | Locations | 5 min |
107
116
  | Employee groups | 5 min |
108
117
  | Standard hours | 5 min |
109
118
  | Roster shifts | 2 min |
119
+ | Kiosks | 5 min |
120
+ | Kiosk staff | 1 min |
110
121
  | Report fields | 10 min |
111
122
 
112
123
  ### Rate Limiting
package/dist/client.d.ts CHANGED
@@ -6,8 +6,9 @@
6
6
  *
7
7
  * @see https://api.keypay.com.au/
8
8
  */
9
- import type { EHConfig, EHEmployeeOptions, EHSingleEmployeeOptions, EHStandardHours, EHEmployeeGroup, EHRosterShift, EHRosterShiftOptions, EHKioskEmployee, EHKioskStaffOptions, EHErrorInfo, EHReportField, EHEmployeeDetailsReportOptions } from './types.js';
9
+ import type { EHConfig, EHEmployeeOptions, EHSingleEmployeeOptions, EHStandardHours, EHLocation, EHEmployeeGroup, EHRosterShift, EHRosterShiftOptions, EHKiosk, EHKioskEmployee, EHKioskStaffOptions, EHReportField, EHEmployeeDetailsReportOptions } from './types.js';
10
10
  import type { EHAuEmployee } from './employee-types.generated.js';
11
+ import type { Result } from '@markwharton/api-core';
11
12
  /**
12
13
  * Employment Hero Payroll API Client
13
14
  *
@@ -19,7 +20,7 @@ import type { EHAuEmployee } from './employee-types.generated.js';
19
20
  * await client.validateApiKey();
20
21
  *
21
22
  * // Get employees
22
- * const { employees } = await client.getEmployees();
23
+ * const { data: employees } = await client.getEmployees();
23
24
  * ```
24
25
  */
25
26
  export declare class EHClient {
@@ -54,38 +55,27 @@ export declare class EHClient {
54
55
  /**
55
56
  * Validate the API key by calling GET /user
56
57
  */
57
- validateApiKey(): Promise<{
58
- valid: boolean;
59
- error?: string;
60
- }>;
58
+ validateApiKey(): Promise<Result<void>>;
61
59
  /**
62
60
  * Get all employees (unstructured format)
63
61
  */
64
- getEmployees(options?: EHEmployeeOptions): Promise<{
65
- employees?: EHAuEmployee[];
66
- error?: EHErrorInfo;
67
- }>;
62
+ getEmployees(options?: EHEmployeeOptions): Promise<Result<EHAuEmployee[]>>;
68
63
  /**
69
64
  * Get a single employee by ID
70
65
  */
71
- getEmployee(employeeId: number, options?: EHSingleEmployeeOptions): Promise<{
72
- employee?: EHAuEmployee;
73
- error?: EHErrorInfo;
74
- }>;
66
+ getEmployee(employeeId: number, options?: EHSingleEmployeeOptions): Promise<Result<EHAuEmployee>>;
75
67
  /**
76
68
  * Get standard hours for an employee (includes FTE value)
77
69
  */
78
- getStandardHours(employeeId: number): Promise<{
79
- standardHours?: EHStandardHours;
80
- error?: EHErrorInfo;
81
- }>;
70
+ getStandardHours(employeeId: number): Promise<Result<EHStandardHours>>;
71
+ /**
72
+ * Get all business locations
73
+ */
74
+ getLocations(): Promise<Result<EHLocation[]>>;
82
75
  /**
83
76
  * Get all employee groups
84
77
  */
85
- getEmployeeGroups(): Promise<{
86
- employeeGroups?: EHEmployeeGroup[];
87
- error?: EHErrorInfo;
88
- }>;
78
+ getEmployeeGroups(): Promise<Result<EHEmployeeGroup[]>>;
89
79
  /**
90
80
  * Get roster shifts for a date range
91
81
  *
@@ -93,38 +83,30 @@ export declare class EHClient {
93
83
  * @param toDate - End date (YYYY-MM-DD)
94
84
  * @param options - Optional filters
95
85
  */
96
- getRosterShifts(fromDate: string, toDate: string, options?: EHRosterShiftOptions): Promise<{
97
- rosterShifts?: EHRosterShift[];
98
- error?: EHErrorInfo;
99
- }>;
86
+ getRosterShifts(fromDate: string, toDate: string, options?: EHRosterShiftOptions): Promise<Result<EHRosterShift[]>>;
87
+ /**
88
+ * Get all kiosks for the business
89
+ */
90
+ getKiosks(): Promise<Result<EHKiosk[]>>;
100
91
  /**
101
92
  * Get kiosk staff for time and attendance
102
93
  *
103
94
  * @param kioskId - Kiosk identifier
104
95
  * @param options - Optional query parameters
105
96
  */
106
- getKioskStaff(kioskId: number, options?: EHKioskStaffOptions): Promise<{
107
- staff?: EHKioskEmployee[];
108
- error?: EHErrorInfo;
109
- }>;
97
+ getKioskStaff(kioskId: number, options?: EHKioskStaffOptions): Promise<Result<EHKioskEmployee[]>>;
110
98
  /**
111
99
  * Get available fields for the Employee Details Report
112
100
  *
113
101
  * Returns the list of columns that can be requested via getEmployeeDetailsReport().
114
102
  * Use this to discover what data is available for the business.
115
103
  */
116
- getReportFields(): Promise<{
117
- fields?: EHReportField[];
118
- error?: EHErrorInfo;
119
- }>;
104
+ getReportFields(): Promise<Result<EHReportField[]>>;
120
105
  /**
121
106
  * Get Employee Details Report
122
107
  *
123
108
  * Returns all employees with the requested columns in a single API call.
124
109
  * The response is dynamic (JObject[]) — field keys depend on selectedColumns.
125
110
  */
126
- getEmployeeDetailsReport(options?: EHEmployeeDetailsReportOptions): Promise<{
127
- records?: Record<string, unknown>[];
128
- error?: EHErrorInfo;
129
- }>;
111
+ getEmployeeDetailsReport(options?: EHEmployeeDetailsReportOptions): Promise<Result<Record<string, unknown>[]>>;
130
112
  }
package/dist/client.js CHANGED
@@ -6,11 +6,11 @@
6
6
  *
7
7
  * @see https://api.keypay.com.au/
8
8
  */
9
- import { AU_EMPLOYEE_OPERATIONAL_FIELDS, AU_EMPLOYEE_FIELDS, EMPLOYEE_GROUP_FIELDS, ROSTER_SHIFT_FIELDS, KIOSK_EMPLOYEE_FIELDS, } from './types.js';
9
+ import { AU_EMPLOYEE_OPERATIONAL_FIELDS, AU_EMPLOYEE_FIELDS, LOCATION_FIELDS, EMPLOYEE_GROUP_FIELDS, ROSTER_SHIFT_FIELDS, KIOSK_FIELDS, KIOSK_EMPLOYEE_FIELDS, } from './types.js';
10
10
  import { buildBasicAuthHeader, pickFields } from './utils.js';
11
11
  import { parseEHErrorResponse } from './errors.js';
12
12
  import { EH_API_BASE, EH_REGION_URLS } from './constants.js';
13
- import { TTLCache, getErrorMessage, fetchWithRetry } from '@markwharton/api-core';
13
+ import { TTLCache, getErrorMessage, fetchWithRetry, ok, err } from '@markwharton/api-core';
14
14
  import { RateLimiter } from './rate-limiter.js';
15
15
  /** Default page size for paginated endpoints */
16
16
  const DEFAULT_PAGE_SIZE = 100;
@@ -28,7 +28,7 @@ const DEFAULT_PAGE_SIZE = 100;
28
28
  * await client.validateApiKey();
29
29
  *
30
30
  * // Get employees
31
- * const { employees } = await client.getEmployees();
31
+ * const { data: employees } = await client.getEmployees();
32
32
  * ```
33
33
  */
34
34
  export class EHClient {
@@ -43,9 +43,11 @@ export class EHClient {
43
43
  }
44
44
  this.cacheTtl = {
45
45
  employeesTtl: config.cache?.employeesTtl ?? 300000,
46
+ locationsTtl: config.cache?.locationsTtl ?? 300000,
46
47
  groupsTtl: config.cache?.groupsTtl ?? 300000,
47
48
  standardHoursTtl: config.cache?.standardHoursTtl ?? 300000,
48
49
  rosterShiftsTtl: config.cache?.rosterShiftsTtl ?? 120000,
50
+ kiosksTtl: config.cache?.kiosksTtl ?? 300000,
49
51
  kioskStaffTtl: config.cache?.kioskStaffTtl ?? 60000,
50
52
  reportFieldsTtl: config.cache?.reportFieldsTtl ?? 600000,
51
53
  };
@@ -120,12 +122,12 @@ export class EHClient {
120
122
  if (!response.ok) {
121
123
  const errorText = await response.text();
122
124
  const { message } = parseEHErrorResponse(errorText, response.status);
123
- return { error: { message, statusCode: response.status } };
125
+ return err(message, response.status);
124
126
  }
125
- return { data: await parse(response) };
127
+ return ok(await parse(response));
126
128
  }
127
129
  catch (error) {
128
- return { error: { message: getErrorMessage(error), statusCode: 0 } };
130
+ return err(getErrorMessage(error), 0);
129
131
  }
130
132
  }
131
133
  // ============================================================================
@@ -139,15 +141,15 @@ export class EHClient {
139
141
  try {
140
142
  const response = await this.fetch(url);
141
143
  if (response.ok) {
142
- return { valid: true };
144
+ return { ok: true };
143
145
  }
144
146
  if (response.status === 401 || response.status === 403) {
145
- return { valid: false, error: 'Invalid or expired API key' };
147
+ return err('Invalid or expired API key', response.status);
146
148
  }
147
- return { valid: false, error: `Unexpected response: HTTP ${response.status}` };
149
+ return err(`Unexpected response: HTTP ${response.status}`, response.status);
148
150
  }
149
151
  catch (error) {
150
- return { valid: false, error: getErrorMessage(error) || 'Connection failed' };
152
+ return err(getErrorMessage(error) || 'Connection failed');
151
153
  }
152
154
  }
153
155
  // ============================================================================
@@ -183,7 +185,7 @@ export class EHClient {
183
185
  if (!response.ok) {
184
186
  const errorText = await response.text();
185
187
  const { message } = parseEHErrorResponse(errorText, response.status);
186
- return { error: { message, statusCode: response.status } };
188
+ return err(message, response.status);
187
189
  }
188
190
  const page = (await response.json())
189
191
  .map(item => pickFields(item, fields));
@@ -192,10 +194,10 @@ export class EHClient {
192
194
  break;
193
195
  skip += DEFAULT_PAGE_SIZE;
194
196
  }
195
- return { employees: allEmployees };
197
+ return ok(allEmployees);
196
198
  }
197
199
  catch (error) {
198
- return { error: { message: getErrorMessage(error), statusCode: 0 } };
200
+ return err(getErrorMessage(error), 0);
199
201
  }
200
202
  });
201
203
  }
@@ -207,10 +209,9 @@ export class EHClient {
207
209
  const cacheKey = options?.includePii ? `employee:${employeeId}:pii` : `employee:${employeeId}`;
208
210
  return this.cached(cacheKey, this.cacheTtl.employeesTtl, async () => {
209
211
  const url = `${this.baseUrl}/business/${this.businessId}/employee/unstructured/${employeeId}`;
210
- const { data, error } = await this.fetchAndParse(url, async (r) => {
212
+ return this.fetchAndParse(url, async (r) => {
211
213
  return pickFields(await r.json(), fields);
212
214
  });
213
- return error ? { error } : { employee: data };
214
215
  });
215
216
  }
216
217
  // ============================================================================
@@ -222,10 +223,46 @@ export class EHClient {
222
223
  async getStandardHours(employeeId) {
223
224
  return this.cached(`standardhours:${employeeId}`, this.cacheTtl.standardHoursTtl, async () => {
224
225
  const url = `${this.baseUrl}/business/${this.businessId}/employee/${employeeId}/standardhours`;
225
- const { data, error } = await this.fetchAndParse(url, async (r) => {
226
+ return this.fetchAndParse(url, async (r) => {
226
227
  return await r.json();
227
228
  }, { description: `Get standard hours for employee ${employeeId}` });
228
- return error ? { error } : { standardHours: data };
229
+ });
230
+ }
231
+ // ============================================================================
232
+ // Locations
233
+ // ============================================================================
234
+ /**
235
+ * Get all business locations
236
+ */
237
+ async getLocations() {
238
+ return this.cached('locations', this.cacheTtl.locationsTtl, async () => {
239
+ const params = new URLSearchParams({
240
+ '$top': String(DEFAULT_PAGE_SIZE),
241
+ });
242
+ try {
243
+ const allLocations = [];
244
+ let skip = 0;
245
+ while (true) {
246
+ params.set('$skip', String(skip));
247
+ const url = `${this.baseUrl}/business/${this.businessId}/location?${params}`;
248
+ const response = await this.fetch(url);
249
+ if (!response.ok) {
250
+ const errorText = await response.text();
251
+ const { message } = parseEHErrorResponse(errorText, response.status);
252
+ return err(message, response.status);
253
+ }
254
+ const page = (await response.json())
255
+ .map(item => pickFields(item, LOCATION_FIELDS));
256
+ allLocations.push(...page);
257
+ if (page.length < DEFAULT_PAGE_SIZE)
258
+ break;
259
+ skip += DEFAULT_PAGE_SIZE;
260
+ }
261
+ return ok(allLocations);
262
+ }
263
+ catch (error) {
264
+ return err(getErrorMessage(error), 0);
265
+ }
229
266
  });
230
267
  }
231
268
  // ============================================================================
@@ -249,7 +286,7 @@ export class EHClient {
249
286
  if (!response.ok) {
250
287
  const errorText = await response.text();
251
288
  const { message } = parseEHErrorResponse(errorText, response.status);
252
- return { error: { message, statusCode: response.status } };
289
+ return err(message, response.status);
253
290
  }
254
291
  const page = (await response.json())
255
292
  .map(item => pickFields(item, EMPLOYEE_GROUP_FIELDS));
@@ -258,10 +295,10 @@ export class EHClient {
258
295
  break;
259
296
  skip += DEFAULT_PAGE_SIZE;
260
297
  }
261
- return { employeeGroups: allGroups };
298
+ return ok(allGroups);
262
299
  }
263
300
  catch (error) {
264
- return { error: { message: getErrorMessage(error), statusCode: 0 } };
301
+ return err(getErrorMessage(error), 0);
265
302
  }
266
303
  });
267
304
  }
@@ -353,7 +390,7 @@ export class EHClient {
353
390
  if (!response.ok) {
354
391
  const errorText = await response.text();
355
392
  const { message } = parseEHErrorResponse(errorText, response.status);
356
- return { error: { message, statusCode: response.status } };
393
+ return err(message, response.status);
357
394
  }
358
395
  const page = (await response.json())
359
396
  .map(item => pickFields(item, ROSTER_SHIFT_FIELDS));
@@ -362,16 +399,50 @@ export class EHClient {
362
399
  break;
363
400
  currentPage++;
364
401
  }
365
- return { rosterShifts: allShifts };
402
+ return ok(allShifts);
366
403
  }
367
404
  catch (error) {
368
- return { error: { message: getErrorMessage(error), statusCode: 0 } };
405
+ return err(getErrorMessage(error), 0);
369
406
  }
370
407
  });
371
408
  }
372
409
  // ============================================================================
373
410
  // Time & Attendance (Kiosk)
374
411
  // ============================================================================
412
+ /**
413
+ * Get all kiosks for the business
414
+ */
415
+ async getKiosks() {
416
+ return this.cached('kiosks', this.cacheTtl.kiosksTtl, async () => {
417
+ const params = new URLSearchParams({
418
+ '$top': String(DEFAULT_PAGE_SIZE),
419
+ });
420
+ try {
421
+ const allKiosks = [];
422
+ let skip = 0;
423
+ while (true) {
424
+ params.set('$skip', String(skip));
425
+ const url = `${this.baseUrl}/business/${this.businessId}/kiosk?${params}`;
426
+ const response = await this.fetch(url);
427
+ if (!response.ok) {
428
+ const errorText = await response.text();
429
+ const { message } = parseEHErrorResponse(errorText, response.status);
430
+ return err(message, response.status);
431
+ }
432
+ const page = (await response.json())
433
+ .map(item => pickFields(item, KIOSK_FIELDS));
434
+ allKiosks.push(...page);
435
+ if (page.length < DEFAULT_PAGE_SIZE)
436
+ break;
437
+ skip += DEFAULT_PAGE_SIZE;
438
+ }
439
+ return ok(allKiosks);
440
+ }
441
+ catch (error) {
442
+ return err(getErrorMessage(error), 0);
443
+ }
444
+ });
445
+ }
375
446
  /**
376
447
  * Get kiosk staff for time and attendance
377
448
  *
@@ -391,11 +462,10 @@ export class EHClient {
391
462
  const url = queryString
392
463
  ? `${this.baseUrl}/business/${this.businessId}/kiosk/${kioskId}/staff?${queryString}`
393
464
  : `${this.baseUrl}/business/${this.businessId}/kiosk/${kioskId}/staff`;
394
- const { data, error } = await this.fetchAndParse(url, async (r) => {
465
+ return this.fetchAndParse(url, async (r) => {
395
466
  return (await r.json())
396
467
  .map(item => pickFields(item, KIOSK_EMPLOYEE_FIELDS));
397
468
  });
398
- return error ? { error } : { staff: data };
399
469
  });
400
470
  }
401
471
  // ============================================================================
@@ -410,10 +480,9 @@ export class EHClient {
410
480
  async getReportFields() {
411
481
  return this.cached('reportfields', this.cacheTtl.reportFieldsTtl, async () => {
412
482
  const url = `${this.baseUrl}/business/${this.businessId}/report/employeedetails/fields`;
413
- const { data, error } = await this.fetchAndParse(url, async (r) => {
483
+ return this.fetchAndParse(url, async (r) => {
414
484
  return await r.json();
415
485
  });
416
- return error ? { error } : { fields: data };
417
486
  });
418
487
  }
419
488
  /**
@@ -441,9 +510,8 @@ export class EHClient {
441
510
  const url = queryString
442
511
  ? `${this.baseUrl}/business/${this.businessId}/report/employeedetails?${queryString}`
443
512
  : `${this.baseUrl}/business/${this.businessId}/report/employeedetails`;
444
- const { data, error } = await this.fetchAndParse(url, async (r) => {
513
+ return this.fetchAndParse(url, async (r) => {
445
514
  return await r.json();
446
515
  });
447
- return error ? { error } : { records: data };
448
516
  }
449
517
  }
package/dist/errors.d.ts CHANGED
@@ -26,10 +26,10 @@ export declare function parseEHErrorResponse(errorText: string, statusCode: numb
26
26
  */
27
27
  export declare class EHError extends Error {
28
28
  /** HTTP status code */
29
- statusCode: number;
29
+ status: number;
30
30
  /** Raw error response */
31
31
  rawResponse?: string;
32
- constructor(message: string, statusCode: number, options?: {
32
+ constructor(message: string, status: number, options?: {
33
33
  rawResponse?: string;
34
34
  });
35
35
  /**
package/dist/errors.js CHANGED
@@ -34,10 +34,10 @@ export function parseEHErrorResponse(errorText, statusCode) {
34
34
  * Custom error class for EH API errors
35
35
  */
36
36
  export class EHError extends Error {
37
- constructor(message, statusCode, options) {
37
+ constructor(message, status, options) {
38
38
  super(message);
39
39
  this.name = 'EHError';
40
- this.statusCode = statusCode;
40
+ this.status = status;
41
41
  this.rawResponse = options?.rawResponse;
42
42
  }
43
43
  /**
package/dist/index.d.ts CHANGED
@@ -13,19 +13,20 @@
13
13
  * await client.validateApiKey();
14
14
  *
15
15
  * // Get employees
16
- * const { employees } = await client.getEmployees();
16
+ * const { data: employees } = await client.getEmployees();
17
17
  *
18
18
  * // Get roster shifts
19
- * const { rosterShifts } = await client.getRosterShifts('2026-02-03', '2026-02-09');
19
+ * const { data: rosterShifts } = await client.getRosterShifts('2026-02-03', '2026-02-09');
20
20
  * ```
21
21
  */
22
22
  export { EHClient } from './client.js';
23
- export type { EHConfig, EHCacheConfig, EHRetryConfig, EHEmployee, EHEmployeeOptions, EHSingleEmployeeOptions, EHStandardHours, EHEmployeeGroup, EHRosterShift, EHRosterShiftOptions, EHAttendanceStatus, EHKioskEmployee, EHKioskStaffOptions, EHReportField, EHEmployeeDetailsReportOptions, EHErrorInfo, } from './types.js';
24
- export { AU_EMPLOYEE_OPERATIONAL_FIELDS, AU_EMPLOYEE_PII_FIELDS, AU_EMPLOYEE_FIELDS, EMPLOYEE_GROUP_FIELDS, ROSTER_SHIFT_FIELDS, KIOSK_EMPLOYEE_FIELDS, } from './types.js';
23
+ export type { EHConfig, EHCacheConfig, EHRetryConfig, EHEmployee, EHEmployeeOptions, EHSingleEmployeeOptions, EHStandardHours, EHLocation, EHEmployeeGroup, EHRosterShift, EHRosterShiftOptions, EHAttendanceStatus, EHKiosk, EHKioskEmployee, EHKioskStaffOptions, EHReportField, EHEmployeeDetailsReportOptions, } from './types.js';
24
+ export { AU_EMPLOYEE_OPERATIONAL_FIELDS, AU_EMPLOYEE_PII_FIELDS, AU_EMPLOYEE_FIELDS, LOCATION_FIELDS, EMPLOYEE_GROUP_FIELDS, ROSTER_SHIFT_FIELDS, KIOSK_FIELDS, KIOSK_EMPLOYEE_FIELDS, } from './types.js';
25
25
  export type { EHAuEmployee } from './employee-types.generated.js';
26
26
  export { RateLimiter } from './rate-limiter.js';
27
27
  export { buildBasicAuthHeader, pickFields } from './utils.js';
28
- export { getErrorMessage } from '@markwharton/api-core';
28
+ export { ok, err, getErrorMessage } from '@markwharton/api-core';
29
+ export type { Result, RetryConfig } from '@markwharton/api-core';
29
30
  export { EH_API_BASE, EH_REGION_URLS } from './constants.js';
30
31
  export type { EHRegion } from './constants.js';
31
32
  export { EHError, parseEHErrorResponse } from './errors.js';
package/dist/index.js CHANGED
@@ -13,21 +13,22 @@
13
13
  * await client.validateApiKey();
14
14
  *
15
15
  * // Get employees
16
- * const { employees } = await client.getEmployees();
16
+ * const { data: employees } = await client.getEmployees();
17
17
  *
18
18
  * // Get roster shifts
19
- * const { rosterShifts } = await client.getRosterShifts('2026-02-03', '2026-02-09');
19
+ * const { data: rosterShifts } = await client.getRosterShifts('2026-02-03', '2026-02-09');
20
20
  * ```
21
21
  */
22
22
  // Main client
23
23
  export { EHClient } from './client.js';
24
24
  // Field key constants (whitelists for pickFields)
25
- export { AU_EMPLOYEE_OPERATIONAL_FIELDS, AU_EMPLOYEE_PII_FIELDS, AU_EMPLOYEE_FIELDS, EMPLOYEE_GROUP_FIELDS, ROSTER_SHIFT_FIELDS, KIOSK_EMPLOYEE_FIELDS, } from './types.js';
25
+ export { AU_EMPLOYEE_OPERATIONAL_FIELDS, AU_EMPLOYEE_PII_FIELDS, AU_EMPLOYEE_FIELDS, LOCATION_FIELDS, EMPLOYEE_GROUP_FIELDS, ROSTER_SHIFT_FIELDS, KIOSK_FIELDS, KIOSK_EMPLOYEE_FIELDS, } from './types.js';
26
26
  // Rate limiting
27
27
  export { RateLimiter } from './rate-limiter.js';
28
28
  // Utilities
29
29
  export { buildBasicAuthHeader, pickFields } from './utils.js';
30
- export { getErrorMessage } from '@markwharton/api-core';
30
+ // Result pattern (re-exported from @markwharton/api-core)
31
+ export { ok, err, getErrorMessage } from '@markwharton/api-core';
31
32
  // Constants
32
33
  export { EH_API_BASE, EH_REGION_URLS } from './constants.js';
33
34
  // Errors
package/dist/types.d.ts CHANGED
@@ -5,6 +5,7 @@
5
5
  * Based on the API reference and KeyPay .NET SDK models.
6
6
  */
7
7
  import type { EHRegion } from './constants.js';
8
+ import type { RetryConfig } from '@markwharton/api-core';
8
9
  /**
9
10
  * Cache configuration for EHClient
10
11
  *
@@ -14,32 +15,23 @@ import type { EHRegion } from './constants.js';
14
15
  export interface EHCacheConfig {
15
16
  /** TTL for employee list (default: 300000 = 5 min) */
16
17
  employeesTtl?: number;
18
+ /** TTL for locations (default: 300000 = 5 min) */
19
+ locationsTtl?: number;
17
20
  /** TTL for employee groups (default: 300000 = 5 min) */
18
21
  groupsTtl?: number;
19
22
  /** TTL for standard hours (default: 300000 = 5 min) */
20
23
  standardHoursTtl?: number;
21
24
  /** TTL for roster shifts (default: 120000 = 2 min) */
22
25
  rosterShiftsTtl?: number;
26
+ /** TTL for kiosks list (default: 300000 = 5 min) */
27
+ kiosksTtl?: number;
23
28
  /** TTL for kiosk staff (default: 60000 = 1 min) */
24
29
  kioskStaffTtl?: number;
25
30
  /** TTL for report fields (default: 600000 = 10 min) */
26
31
  reportFieldsTtl?: number;
27
32
  }
28
- /**
29
- * Retry configuration for EHClient
30
- *
31
- * Controls automatic retry behavior for transient failures
32
- * (HTTP 429 Too Many Requests, 503 Service Unavailable).
33
- * Uses exponential backoff with optional Retry-After header support.
34
- */
35
- export interface EHRetryConfig {
36
- /** Maximum number of retry attempts (default: 3) */
37
- maxRetries?: number;
38
- /** Initial delay in milliseconds before first retry (default: 1000) */
39
- initialDelayMs?: number;
40
- /** Maximum delay cap in milliseconds (default: 10000) */
41
- maxDelayMs?: number;
42
- }
33
+ /** @deprecated Use `RetryConfig` from `@markwharton/api-core` directly. */
34
+ export type EHRetryConfig = RetryConfig;
43
35
  /**
44
36
  * Employment Hero Payroll configuration for API access
45
37
  */
@@ -90,6 +82,31 @@ export interface EHStandardHours {
90
82
  /** Full-time equivalent hours (the FTE value) */
91
83
  fullTimeEquivalentHours: number | null;
92
84
  }
85
+ /**
86
+ * Business location
87
+ *
88
+ * From GET /business/{id}/location
89
+ */
90
+ export interface EHLocation {
91
+ /** Location ID */
92
+ id: number;
93
+ /** Parent location ID (for hierarchy) */
94
+ parentId: number | null;
95
+ /** Location name */
96
+ name: string;
97
+ /** External identifier */
98
+ externalId: string | null;
99
+ /** Source system that created this location */
100
+ source: string | null;
101
+ /** Fully qualified name (includes parent hierarchy) */
102
+ fullyQualifiedName: string | null;
103
+ /** Whether this is a global/default location */
104
+ isGlobal: boolean;
105
+ /** Australian state */
106
+ state: string | null;
107
+ /** Country */
108
+ country: string | null;
109
+ }
93
110
  /**
94
111
  * Employee group
95
112
  */
@@ -212,6 +229,45 @@ export interface EHRosterShiftOptions {
212
229
  /** Include warning data in response */
213
230
  includeWarnings?: boolean;
214
231
  }
232
+ /**
233
+ * Kiosk configuration
234
+ *
235
+ * From GET /business/{id}/kiosk
236
+ */
237
+ export interface EHKiosk {
238
+ /** Kiosk ID */
239
+ id: number;
240
+ /** External identifier */
241
+ externalId: string | null;
242
+ /** Kiosk name */
243
+ name: string;
244
+ /** Associated location ID */
245
+ locationId: number | null;
246
+ /** Windows timezone name */
247
+ timeZone: string | null;
248
+ /** IANA timezone name (read-only) */
249
+ ianaTimeZone: string | null;
250
+ /** Allow higher classification selection */
251
+ allowHigherClassificationSelection: boolean;
252
+ /** Whether location is required for clock on */
253
+ isLocationRequired: boolean;
254
+ /** Whether work type is required for clock on */
255
+ isWorkTypeRequired: boolean;
256
+ /** Restrict locations for employees */
257
+ restrictLocationsForEmployees: boolean;
258
+ /** Allow employee shift selection */
259
+ allowEmployeeShiftSelection: boolean | null;
260
+ /** Clock on window in minutes */
261
+ clockOnWindowMinutes: number | null;
262
+ /** Clock off window in minutes */
263
+ clockOffWindowMinutes: number | null;
264
+ /** Whether photo is required for clock on */
265
+ isPhotoRequired: boolean | null;
266
+ /** Whether kiosk can add employees */
267
+ canAddEmployees: boolean;
268
+ /** Whether paid breaks are enabled */
269
+ paidBreaksEnabled: boolean;
270
+ }
215
271
  /**
216
272
  * Options for getKioskStaff
217
273
  */
@@ -245,15 +301,10 @@ export interface EHEmployeeDetailsReportOptions {
245
301
  /** Include inactive employees (default: false) */
246
302
  includeInactive?: boolean;
247
303
  }
248
- /**
249
- * Structured error information from EH API
250
- */
251
- export interface EHErrorInfo {
252
- /** Human-readable error message */
253
- message: string;
254
- /** HTTP status code from the response */
255
- statusCode: number;
256
- }
304
+ /** Whitelisted fields for EHLocation */
305
+ export declare const LOCATION_FIELDS: readonly ["id", "parentId", "name", "externalId", "source", "fullyQualifiedName", "isGlobal", "state", "country"];
306
+ /** Whitelisted fields for EHKiosk */
307
+ export declare const KIOSK_FIELDS: readonly ["id", "externalId", "name", "locationId", "timeZone", "ianaTimeZone", "allowHigherClassificationSelection", "isLocationRequired", "isWorkTypeRequired", "restrictLocationsForEmployees", "allowEmployeeShiftSelection", "clockOnWindowMinutes", "clockOffWindowMinutes", "isPhotoRequired", "canAddEmployees", "paidBreaksEnabled"];
257
308
  /** Whitelisted fields for EHKioskEmployee */
258
309
  export declare const KIOSK_EMPLOYEE_FIELDS: readonly ["employeeId", "firstName", "surname", "name", "status", "clockOnTimeUtc", "breakStartTimeUtc", "currentShiftId", "employeeStartDate", "employeeGroupIds", "longShift", "recordedTimeUtc"];
259
310
  /** Whitelisted fields for EHEmployeeGroup */
package/dist/types.js CHANGED
@@ -7,6 +7,19 @@
7
7
  // ============================================================================
8
8
  // Field Key Constants (whitelists for pickFields)
9
9
  // ============================================================================
10
+ /** Whitelisted fields for EHLocation */
11
+ export const LOCATION_FIELDS = [
12
+ 'id', 'parentId', 'name', 'externalId', 'source',
13
+ 'fullyQualifiedName', 'isGlobal', 'state', 'country',
14
+ ];
15
+ /** Whitelisted fields for EHKiosk */
16
+ export const KIOSK_FIELDS = [
17
+ 'id', 'externalId', 'name', 'locationId', 'timeZone', 'ianaTimeZone',
18
+ 'allowHigherClassificationSelection', 'isLocationRequired', 'isWorkTypeRequired',
19
+ 'restrictLocationsForEmployees', 'allowEmployeeShiftSelection',
20
+ 'clockOnWindowMinutes', 'clockOffWindowMinutes',
21
+ 'isPhotoRequired', 'canAddEmployees', 'paidBreaksEnabled',
22
+ ];
10
23
  /** Whitelisted fields for EHKioskEmployee */
11
24
  export const KIOSK_EMPLOYEE_FIELDS = [
12
25
  'employeeId', 'firstName', 'surname', 'name', 'status',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markwharton/eh-payroll",
3
- "version": "1.2.0",
3
+ "version": "2.0.0",
4
4
  "description": "Employment Hero Payroll API client",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -16,7 +16,7 @@
16
16
  "clean": "rm -rf dist"
17
17
  },
18
18
  "dependencies": {
19
- "@markwharton/api-core": "^1.0.0"
19
+ "@markwharton/api-core": "^1.1.0"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@types/node": "^20.10.0",