@markwharton/eh-payroll 1.3.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/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, EHLocation, EHEmployeeGroup, EHRosterShift, EHRosterShiftOptions, EHKiosk, 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,45 +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>>;
82
71
  /**
83
72
  * Get all business locations
84
73
  */
85
- getLocations(): Promise<{
86
- locations?: EHLocation[];
87
- error?: EHErrorInfo;
88
- }>;
74
+ getLocations(): Promise<Result<EHLocation[]>>;
89
75
  /**
90
76
  * Get all employee groups
91
77
  */
92
- getEmployeeGroups(): Promise<{
93
- employeeGroups?: EHEmployeeGroup[];
94
- error?: EHErrorInfo;
95
- }>;
78
+ getEmployeeGroups(): Promise<Result<EHEmployeeGroup[]>>;
96
79
  /**
97
80
  * Get roster shifts for a date range
98
81
  *
@@ -100,45 +83,30 @@ export declare class EHClient {
100
83
  * @param toDate - End date (YYYY-MM-DD)
101
84
  * @param options - Optional filters
102
85
  */
103
- getRosterShifts(fromDate: string, toDate: string, options?: EHRosterShiftOptions): Promise<{
104
- rosterShifts?: EHRosterShift[];
105
- error?: EHErrorInfo;
106
- }>;
86
+ getRosterShifts(fromDate: string, toDate: string, options?: EHRosterShiftOptions): Promise<Result<EHRosterShift[]>>;
107
87
  /**
108
88
  * Get all kiosks for the business
109
89
  */
110
- getKiosks(): Promise<{
111
- kiosks?: EHKiosk[];
112
- error?: EHErrorInfo;
113
- }>;
90
+ getKiosks(): Promise<Result<EHKiosk[]>>;
114
91
  /**
115
92
  * Get kiosk staff for time and attendance
116
93
  *
117
94
  * @param kioskId - Kiosk identifier
118
95
  * @param options - Optional query parameters
119
96
  */
120
- getKioskStaff(kioskId: number, options?: EHKioskStaffOptions): Promise<{
121
- staff?: EHKioskEmployee[];
122
- error?: EHErrorInfo;
123
- }>;
97
+ getKioskStaff(kioskId: number, options?: EHKioskStaffOptions): Promise<Result<EHKioskEmployee[]>>;
124
98
  /**
125
99
  * Get available fields for the Employee Details Report
126
100
  *
127
101
  * Returns the list of columns that can be requested via getEmployeeDetailsReport().
128
102
  * Use this to discover what data is available for the business.
129
103
  */
130
- getReportFields(): Promise<{
131
- fields?: EHReportField[];
132
- error?: EHErrorInfo;
133
- }>;
104
+ getReportFields(): Promise<Result<EHReportField[]>>;
134
105
  /**
135
106
  * Get Employee Details Report
136
107
  *
137
108
  * Returns all employees with the requested columns in a single API call.
138
109
  * The response is dynamic (JObject[]) — field keys depend on selectedColumns.
139
110
  */
140
- getEmployeeDetailsReport(options?: EHEmployeeDetailsReportOptions): Promise<{
141
- records?: Record<string, unknown>[];
142
- error?: EHErrorInfo;
143
- }>;
111
+ getEmployeeDetailsReport(options?: EHEmployeeDetailsReportOptions): Promise<Result<Record<string, unknown>[]>>;
144
112
  }
package/dist/client.js CHANGED
@@ -10,7 +10,7 @@ import { AU_EMPLOYEE_OPERATIONAL_FIELDS, AU_EMPLOYEE_FIELDS, LOCATION_FIELDS, EM
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 {
@@ -122,12 +122,12 @@ export class EHClient {
122
122
  if (!response.ok) {
123
123
  const errorText = await response.text();
124
124
  const { message } = parseEHErrorResponse(errorText, response.status);
125
- return { error: { message, statusCode: response.status } };
125
+ return err(message, response.status);
126
126
  }
127
- return { data: await parse(response) };
127
+ return ok(await parse(response));
128
128
  }
129
129
  catch (error) {
130
- return { error: { message: getErrorMessage(error), statusCode: 0 } };
130
+ return err(getErrorMessage(error), 0);
131
131
  }
132
132
  }
133
133
  // ============================================================================
@@ -141,15 +141,15 @@ export class EHClient {
141
141
  try {
142
142
  const response = await this.fetch(url);
143
143
  if (response.ok) {
144
- return { valid: true };
144
+ return { ok: true };
145
145
  }
146
146
  if (response.status === 401 || response.status === 403) {
147
- return { valid: false, error: 'Invalid or expired API key' };
147
+ return err('Invalid or expired API key', response.status);
148
148
  }
149
- return { valid: false, error: `Unexpected response: HTTP ${response.status}` };
149
+ return err(`Unexpected response: HTTP ${response.status}`, response.status);
150
150
  }
151
151
  catch (error) {
152
- return { valid: false, error: getErrorMessage(error) || 'Connection failed' };
152
+ return err(getErrorMessage(error) || 'Connection failed');
153
153
  }
154
154
  }
155
155
  // ============================================================================
@@ -185,7 +185,7 @@ export class EHClient {
185
185
  if (!response.ok) {
186
186
  const errorText = await response.text();
187
187
  const { message } = parseEHErrorResponse(errorText, response.status);
188
- return { error: { message, statusCode: response.status } };
188
+ return err(message, response.status);
189
189
  }
190
190
  const page = (await response.json())
191
191
  .map(item => pickFields(item, fields));
@@ -194,10 +194,10 @@ export class EHClient {
194
194
  break;
195
195
  skip += DEFAULT_PAGE_SIZE;
196
196
  }
197
- return { employees: allEmployees };
197
+ return ok(allEmployees);
198
198
  }
199
199
  catch (error) {
200
- return { error: { message: getErrorMessage(error), statusCode: 0 } };
200
+ return err(getErrorMessage(error), 0);
201
201
  }
202
202
  });
203
203
  }
@@ -209,10 +209,9 @@ export class EHClient {
209
209
  const cacheKey = options?.includePii ? `employee:${employeeId}:pii` : `employee:${employeeId}`;
210
210
  return this.cached(cacheKey, this.cacheTtl.employeesTtl, async () => {
211
211
  const url = `${this.baseUrl}/business/${this.businessId}/employee/unstructured/${employeeId}`;
212
- const { data, error } = await this.fetchAndParse(url, async (r) => {
212
+ return this.fetchAndParse(url, async (r) => {
213
213
  return pickFields(await r.json(), fields);
214
214
  });
215
- return error ? { error } : { employee: data };
216
215
  });
217
216
  }
218
217
  // ============================================================================
@@ -224,10 +223,9 @@ export class EHClient {
224
223
  async getStandardHours(employeeId) {
225
224
  return this.cached(`standardhours:${employeeId}`, this.cacheTtl.standardHoursTtl, async () => {
226
225
  const url = `${this.baseUrl}/business/${this.businessId}/employee/${employeeId}/standardhours`;
227
- const { data, error } = await this.fetchAndParse(url, async (r) => {
226
+ return this.fetchAndParse(url, async (r) => {
228
227
  return await r.json();
229
228
  }, { description: `Get standard hours for employee ${employeeId}` });
230
- return error ? { error } : { standardHours: data };
231
229
  });
232
230
  }
233
231
  // ============================================================================
@@ -251,7 +249,7 @@ export class EHClient {
251
249
  if (!response.ok) {
252
250
  const errorText = await response.text();
253
251
  const { message } = parseEHErrorResponse(errorText, response.status);
254
- return { error: { message, statusCode: response.status } };
252
+ return err(message, response.status);
255
253
  }
256
254
  const page = (await response.json())
257
255
  .map(item => pickFields(item, LOCATION_FIELDS));
@@ -260,10 +258,10 @@ export class EHClient {
260
258
  break;
261
259
  skip += DEFAULT_PAGE_SIZE;
262
260
  }
263
- return { locations: allLocations };
261
+ return ok(allLocations);
264
262
  }
265
263
  catch (error) {
266
- return { error: { message: getErrorMessage(error), statusCode: 0 } };
264
+ return err(getErrorMessage(error), 0);
267
265
  }
268
266
  });
269
267
  }
@@ -288,7 +286,7 @@ export class EHClient {
288
286
  if (!response.ok) {
289
287
  const errorText = await response.text();
290
288
  const { message } = parseEHErrorResponse(errorText, response.status);
291
- return { error: { message, statusCode: response.status } };
289
+ return err(message, response.status);
292
290
  }
293
291
  const page = (await response.json())
294
292
  .map(item => pickFields(item, EMPLOYEE_GROUP_FIELDS));
@@ -297,10 +295,10 @@ export class EHClient {
297
295
  break;
298
296
  skip += DEFAULT_PAGE_SIZE;
299
297
  }
300
- return { employeeGroups: allGroups };
298
+ return ok(allGroups);
301
299
  }
302
300
  catch (error) {
303
- return { error: { message: getErrorMessage(error), statusCode: 0 } };
301
+ return err(getErrorMessage(error), 0);
304
302
  }
305
303
  });
306
304
  }
@@ -392,7 +390,7 @@ export class EHClient {
392
390
  if (!response.ok) {
393
391
  const errorText = await response.text();
394
392
  const { message } = parseEHErrorResponse(errorText, response.status);
395
- return { error: { message, statusCode: response.status } };
393
+ return err(message, response.status);
396
394
  }
397
395
  const page = (await response.json())
398
396
  .map(item => pickFields(item, ROSTER_SHIFT_FIELDS));
@@ -401,10 +399,10 @@ export class EHClient {
401
399
  break;
402
400
  currentPage++;
403
401
  }
404
- return { rosterShifts: allShifts };
402
+ return ok(allShifts);
405
403
  }
406
404
  catch (error) {
407
- return { error: { message: getErrorMessage(error), statusCode: 0 } };
405
+ return err(getErrorMessage(error), 0);
408
406
  }
409
407
  });
410
408
  }
@@ -429,7 +427,7 @@ export class EHClient {
429
427
  if (!response.ok) {
430
428
  const errorText = await response.text();
431
429
  const { message } = parseEHErrorResponse(errorText, response.status);
432
- return { error: { message, statusCode: response.status } };
430
+ return err(message, response.status);
433
431
  }
434
432
  const page = (await response.json())
435
433
  .map(item => pickFields(item, KIOSK_FIELDS));
@@ -438,10 +436,10 @@ export class EHClient {
438
436
  break;
439
437
  skip += DEFAULT_PAGE_SIZE;
440
438
  }
441
- return { kiosks: allKiosks };
439
+ return ok(allKiosks);
442
440
  }
443
441
  catch (error) {
444
- return { error: { message: getErrorMessage(error), statusCode: 0 } };
442
+ return err(getErrorMessage(error), 0);
445
443
  }
446
444
  });
447
445
  }
@@ -464,11 +462,10 @@ export class EHClient {
464
462
  const url = queryString
465
463
  ? `${this.baseUrl}/business/${this.businessId}/kiosk/${kioskId}/staff?${queryString}`
466
464
  : `${this.baseUrl}/business/${this.businessId}/kiosk/${kioskId}/staff`;
467
- const { data, error } = await this.fetchAndParse(url, async (r) => {
465
+ return this.fetchAndParse(url, async (r) => {
468
466
  return (await r.json())
469
467
  .map(item => pickFields(item, KIOSK_EMPLOYEE_FIELDS));
470
468
  });
471
- return error ? { error } : { staff: data };
472
469
  });
473
470
  }
474
471
  // ============================================================================
@@ -483,10 +480,9 @@ export class EHClient {
483
480
  async getReportFields() {
484
481
  return this.cached('reportfields', this.cacheTtl.reportFieldsTtl, async () => {
485
482
  const url = `${this.baseUrl}/business/${this.businessId}/report/employeedetails/fields`;
486
- const { data, error } = await this.fetchAndParse(url, async (r) => {
483
+ return this.fetchAndParse(url, async (r) => {
487
484
  return await r.json();
488
485
  });
489
- return error ? { error } : { fields: data };
490
486
  });
491
487
  }
492
488
  /**
@@ -514,9 +510,8 @@ export class EHClient {
514
510
  const url = queryString
515
511
  ? `${this.baseUrl}/business/${this.businessId}/report/employeedetails?${queryString}`
516
512
  : `${this.baseUrl}/business/${this.businessId}/report/employeedetails`;
517
- const { data, error } = await this.fetchAndParse(url, async (r) => {
513
+ return this.fetchAndParse(url, async (r) => {
518
514
  return await r.json();
519
515
  });
520
- return error ? { error } : { records: data };
521
516
  }
522
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, EHLocation, EHEmployeeGroup, EHRosterShift, EHRosterShiftOptions, EHAttendanceStatus, EHKiosk, EHKioskEmployee, EHKioskStaffOptions, EHReportField, EHEmployeeDetailsReportOptions, EHErrorInfo, } 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
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,10 +13,10 @@
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
@@ -27,7 +27,8 @@ export { AU_EMPLOYEE_OPERATIONAL_FIELDS, AU_EMPLOYEE_PII_FIELDS, AU_EMPLOYEE_FIE
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
  *
@@ -29,21 +30,8 @@ export interface EHCacheConfig {
29
30
  /** TTL for report fields (default: 600000 = 10 min) */
30
31
  reportFieldsTtl?: number;
31
32
  }
32
- /**
33
- * Retry configuration for EHClient
34
- *
35
- * Controls automatic retry behavior for transient failures
36
- * (HTTP 429 Too Many Requests, 503 Service Unavailable).
37
- * Uses exponential backoff with optional Retry-After header support.
38
- */
39
- export interface EHRetryConfig {
40
- /** Maximum number of retry attempts (default: 3) */
41
- maxRetries?: number;
42
- /** Initial delay in milliseconds before first retry (default: 1000) */
43
- initialDelayMs?: number;
44
- /** Maximum delay cap in milliseconds (default: 10000) */
45
- maxDelayMs?: number;
46
- }
33
+ /** @deprecated Use `RetryConfig` from `@markwharton/api-core` directly. */
34
+ export type EHRetryConfig = RetryConfig;
47
35
  /**
48
36
  * Employment Hero Payroll configuration for API access
49
37
  */
@@ -313,15 +301,6 @@ export interface EHEmployeeDetailsReportOptions {
313
301
  /** Include inactive employees (default: false) */
314
302
  includeInactive?: boolean;
315
303
  }
316
- /**
317
- * Structured error information from EH API
318
- */
319
- export interface EHErrorInfo {
320
- /** Human-readable error message */
321
- message: string;
322
- /** HTTP status code from the response */
323
- statusCode: number;
324
- }
325
304
  /** Whitelisted fields for EHLocation */
326
305
  export declare const LOCATION_FIELDS: readonly ["id", "parentId", "name", "externalId", "source", "fullyQualifiedName", "isGlobal", "state", "country"];
327
306
  /** Whitelisted fields for EHKiosk */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markwharton/eh-payroll",
3
- "version": "1.3.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",