@markwharton/eh-payroll 1.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.
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @markwharton/eh-payroll
3
+ *
4
+ * Employment Hero Payroll API client.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * import { EHClient } from '@markwharton/eh-payroll';
9
+ *
10
+ * const client = new EHClient({ apiKey: 'xxx', businessId: 123 });
11
+ *
12
+ * // Validate credentials
13
+ * await client.validateApiKey();
14
+ *
15
+ * // Get employees
16
+ * const { employees } = await client.getEmployees();
17
+ *
18
+ * // Get roster shifts
19
+ * const { shifts } = await client.getRosterShifts('2026-02-03', '2026-02-09');
20
+ * ```
21
+ */
22
+ export { EHClient } from './client.js';
23
+ export type { EHConfig, EHCacheConfig, EHRetryConfig, EHEmployee, EHEmployeeOptions, EHStandardHours, EHEmployeeGroup, EHRosterShift, EHRosterShiftOptions, EHAttendanceStatus, EHKioskEmployee, EHKioskStaffOptions, EHReportField, EHEmployeeDetailsReportOptions, EHErrorInfo, } from './types.js';
24
+ export type { EHAuEmployee } from './employee-types.generated.js';
25
+ export { RateLimiter } from './rate-limiter.js';
26
+ export { buildBasicAuthHeader } from './utils.js';
27
+ export { getErrorMessage } from '@markwharton/api-core';
28
+ export { EH_API_BASE, EH_REGION_URLS } from './constants.js';
29
+ export type { EHRegion } from './constants.js';
30
+ export { EHError, parseEHErrorResponse } from './errors.js';
31
+ export type { EHParsedError } from './errors.js';
package/dist/index.js ADDED
@@ -0,0 +1,32 @@
1
+ /**
2
+ * @markwharton/eh-payroll
3
+ *
4
+ * Employment Hero Payroll API client.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * import { EHClient } from '@markwharton/eh-payroll';
9
+ *
10
+ * const client = new EHClient({ apiKey: 'xxx', businessId: 123 });
11
+ *
12
+ * // Validate credentials
13
+ * await client.validateApiKey();
14
+ *
15
+ * // Get employees
16
+ * const { employees } = await client.getEmployees();
17
+ *
18
+ * // Get roster shifts
19
+ * const { shifts } = await client.getRosterShifts('2026-02-03', '2026-02-09');
20
+ * ```
21
+ */
22
+ // Main client
23
+ export { EHClient } from './client.js';
24
+ // Rate limiting
25
+ export { RateLimiter } from './rate-limiter.js';
26
+ // Utilities
27
+ export { buildBasicAuthHeader } from './utils.js';
28
+ export { getErrorMessage } from '@markwharton/api-core';
29
+ // Constants
30
+ export { EH_API_BASE, EH_REGION_URLS } from './constants.js';
31
+ // Errors
32
+ export { EHError, parseEHErrorResponse } from './errors.js';
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Sliding window rate limiter
3
+ *
4
+ * Enforces a maximum number of requests per time window.
5
+ * Callers that exceed the limit are queued and released when capacity opens.
6
+ *
7
+ * Uses a sliding window approach: tracks timestamps of recent requests and
8
+ * checks that no more than `maxRequests` fall within any `windowMs` period.
9
+ *
10
+ * Thread-safe in single-threaded JS — no races between concurrent acquire() calls.
11
+ */
12
+ export declare class RateLimiter {
13
+ private maxRequests;
14
+ private windowMs;
15
+ private timestamps;
16
+ private queue;
17
+ private timer;
18
+ /**
19
+ * @param maxRequests - Maximum requests allowed per window
20
+ * @param windowMs - Window size in milliseconds (default: 1000)
21
+ */
22
+ constructor(maxRequests: number, windowMs?: number);
23
+ /**
24
+ * Acquire a rate limit token.
25
+ *
26
+ * Resolves immediately if under the limit, otherwise queues until capacity opens.
27
+ */
28
+ acquire(): Promise<void>;
29
+ /** Remove timestamps outside the current window */
30
+ private pruneTimestamps;
31
+ /** Schedule a flush to release queued callers when the oldest timestamp expires */
32
+ private scheduleFlush;
33
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Sliding window rate limiter
3
+ *
4
+ * Enforces a maximum number of requests per time window.
5
+ * Callers that exceed the limit are queued and released when capacity opens.
6
+ *
7
+ * Uses a sliding window approach: tracks timestamps of recent requests and
8
+ * checks that no more than `maxRequests` fall within any `windowMs` period.
9
+ *
10
+ * Thread-safe in single-threaded JS — no races between concurrent acquire() calls.
11
+ */
12
+ export class RateLimiter {
13
+ /**
14
+ * @param maxRequests - Maximum requests allowed per window
15
+ * @param windowMs - Window size in milliseconds (default: 1000)
16
+ */
17
+ constructor(maxRequests, windowMs = 1000) {
18
+ this.maxRequests = maxRequests;
19
+ this.windowMs = windowMs;
20
+ this.timestamps = [];
21
+ this.queue = [];
22
+ this.timer = null;
23
+ }
24
+ /**
25
+ * Acquire a rate limit token.
26
+ *
27
+ * Resolves immediately if under the limit, otherwise queues until capacity opens.
28
+ */
29
+ acquire() {
30
+ this.pruneTimestamps();
31
+ if (this.timestamps.length < this.maxRequests) {
32
+ this.timestamps.push(Date.now());
33
+ return Promise.resolve();
34
+ }
35
+ return new Promise(resolve => {
36
+ this.queue.push({ resolve });
37
+ this.scheduleFlush();
38
+ });
39
+ }
40
+ /** Remove timestamps outside the current window */
41
+ pruneTimestamps() {
42
+ const cutoff = Date.now() - this.windowMs;
43
+ this.timestamps = this.timestamps.filter(t => t > cutoff);
44
+ }
45
+ /** Schedule a flush to release queued callers when the oldest timestamp expires */
46
+ scheduleFlush() {
47
+ if (this.timer || this.queue.length === 0 || this.timestamps.length === 0)
48
+ return;
49
+ const oldest = this.timestamps[0];
50
+ const delay = Math.max(0, this.windowMs - (Date.now() - oldest) + 1);
51
+ this.timer = setTimeout(() => {
52
+ this.timer = null;
53
+ this.pruneTimestamps();
54
+ while (this.queue.length > 0 && this.timestamps.length < this.maxRequests) {
55
+ this.timestamps.push(Date.now());
56
+ this.queue.shift().resolve();
57
+ }
58
+ if (this.queue.length > 0) {
59
+ this.scheduleFlush();
60
+ }
61
+ }, delay);
62
+ }
63
+ }
@@ -0,0 +1,241 @@
1
+ /**
2
+ * Employment Hero Payroll Type Definitions
3
+ *
4
+ * Types for the Employment Hero Payroll (KeyPay) API.
5
+ * Based on the API reference and KeyPay .NET SDK models.
6
+ */
7
+ import type { EHRegion } from './constants.js';
8
+ /**
9
+ * Cache configuration for EHClient
10
+ *
11
+ * When provided to EHConfig, enables in-memory TTL caching of API responses.
12
+ * All TTL values are in milliseconds. Omit individual TTLs to use defaults.
13
+ */
14
+ export interface EHCacheConfig {
15
+ /** TTL for employee list (default: 300000 = 5 min) */
16
+ employeesTtl?: number;
17
+ /** TTL for employee groups (default: 300000 = 5 min) */
18
+ groupsTtl?: number;
19
+ /** TTL for standard hours (default: 300000 = 5 min) */
20
+ standardHoursTtl?: number;
21
+ /** TTL for roster shifts (default: 120000 = 2 min) */
22
+ rosterShiftsTtl?: number;
23
+ /** TTL for report fields (default: 600000 = 10 min) */
24
+ reportFieldsTtl?: number;
25
+ }
26
+ /**
27
+ * Retry configuration for EHClient
28
+ *
29
+ * Controls automatic retry behavior for transient failures
30
+ * (HTTP 429 Too Many Requests, 503 Service Unavailable).
31
+ * Uses exponential backoff with optional Retry-After header support.
32
+ */
33
+ export interface EHRetryConfig {
34
+ /** Maximum number of retry attempts (default: 3) */
35
+ maxRetries?: number;
36
+ /** Initial delay in milliseconds before first retry (default: 1000) */
37
+ initialDelayMs?: number;
38
+ /** Maximum delay cap in milliseconds (default: 10000) */
39
+ maxDelayMs?: number;
40
+ }
41
+ /**
42
+ * Employment Hero Payroll configuration for API access
43
+ */
44
+ export interface EHConfig {
45
+ /** API key for authentication (used as Basic Auth username) */
46
+ apiKey: string;
47
+ /** Business ID to operate on */
48
+ businessId: number;
49
+ /** API region (default: 'au'). Sets the base URL automatically. */
50
+ region?: EHRegion;
51
+ /** Override base URL directly (takes priority over region) */
52
+ baseUrl?: string;
53
+ /** Optional callback invoked on each API request (for debugging/logging) */
54
+ onRequest?: (info: {
55
+ method: string;
56
+ url: string;
57
+ description?: string;
58
+ }) => void;
59
+ /** Enable caching with optional TTL overrides. Omit to disable caching. */
60
+ cache?: EHCacheConfig;
61
+ /** Retry configuration for transient failures (429, 503). Omit to disable retry. */
62
+ retry?: EHRetryConfig;
63
+ /** Max requests per second (default: 5 per API spec). Set 0 to disable. */
64
+ rateLimitPerSecond?: number;
65
+ }
66
+ /**
67
+ * Employee from unstructured endpoint
68
+ *
69
+ * Region-specific types are generated from Swagger specs.
70
+ * EHEmployee is an alias for EHAuEmployee for backward compatibility.
71
+ *
72
+ * @see employee-types.generated.ts
73
+ */
74
+ export type { EHAuEmployee as EHEmployee } from './employee-types.generated.js';
75
+ /**
76
+ * Standard hours for an employee
77
+ *
78
+ * From GET /business/{id}/employee/{eid}/standardhours
79
+ * Contains the FullTimeEquivalentHours field needed for FTE calculations.
80
+ */
81
+ export interface EHStandardHours {
82
+ /** Employee ID */
83
+ employeeId: number;
84
+ /** Standard hours per week */
85
+ standardHoursPerWeek: number;
86
+ /** Standard hours per day */
87
+ standardHoursPerDay: number;
88
+ /** Full-time equivalent hours (the FTE value) */
89
+ fullTimeEquivalentHours: number | null;
90
+ }
91
+ /**
92
+ * Employee group
93
+ */
94
+ export interface EHEmployeeGroup {
95
+ /** Group ID */
96
+ id: number;
97
+ /** Group name */
98
+ name: string;
99
+ }
100
+ /**
101
+ * Roster shift from EH API
102
+ *
103
+ * From GET /business/{id}/rostershift
104
+ */
105
+ export interface EHRosterShift {
106
+ /** Shift ID */
107
+ id: number;
108
+ /** Employee ID (null for unassigned shifts) */
109
+ employeeId: number | null;
110
+ /** Employee name */
111
+ employeeName: string | null;
112
+ /** Location ID */
113
+ locationId: number | null;
114
+ /** Location name */
115
+ locationName: string | null;
116
+ /** Work type ID */
117
+ workTypeId: number | null;
118
+ /** Work type name */
119
+ workTypeName: string | null;
120
+ /** Shift start time (ISO 8601) */
121
+ startTime: string;
122
+ /** Shift end time (ISO 8601) */
123
+ endTime: string;
124
+ /** Shift notes */
125
+ notes: string | null;
126
+ /** Whether the shift has been published */
127
+ published: boolean;
128
+ /** Whether the shift has been accepted */
129
+ accepted: boolean;
130
+ }
131
+ /**
132
+ * Time and attendance status
133
+ */
134
+ export type EHAttendanceStatus = 'NotClockedOn' | 'ClockedOn' | 'OnBreak' | 'ClockedOff';
135
+ /**
136
+ * Kiosk employee from time and attendance
137
+ *
138
+ * From GET /business/{id}/kiosk/{kid}/staff
139
+ */
140
+ export interface EHKioskEmployee {
141
+ /** Employee ID */
142
+ employeeId: number;
143
+ /** First name */
144
+ firstName: string | null;
145
+ /** Surname */
146
+ surname: string | null;
147
+ /** Full name */
148
+ name: string | null;
149
+ /** Attendance status */
150
+ status: EHAttendanceStatus;
151
+ /** Clock-on time in UTC */
152
+ clockOnTimeUtc: string | null;
153
+ /** Break start time in UTC */
154
+ breakStartTimeUtc: string | null;
155
+ /** Current shift ID */
156
+ currentShiftId: number | null;
157
+ /** Employee start date */
158
+ employeeStartDate: string | null;
159
+ /** Employee group IDs */
160
+ employeeGroupIds: number[];
161
+ }
162
+ /**
163
+ * Options for getEmployees
164
+ */
165
+ export interface EHEmployeeOptions {
166
+ /** Filter by pay schedule ID */
167
+ payScheduleId?: number;
168
+ /** Filter by location ID */
169
+ locationId?: number;
170
+ }
171
+ /**
172
+ * Options for getRosterShifts
173
+ */
174
+ export interface EHRosterShiftOptions {
175
+ /** Filter by employee ID */
176
+ employeeId?: number;
177
+ /** Filter by location ID */
178
+ locationId?: number;
179
+ /** Filter by employee group ID */
180
+ employeeGroupId?: number;
181
+ /** Include shifts with all roles (default: only unassigned roles) */
182
+ selectAllRoles?: boolean;
183
+ /** Filter by single shift status */
184
+ shiftStatus?: 'All' | 'Published' | 'Unpublished' | 'Accepted';
185
+ /** Filter by multiple shift statuses */
186
+ shiftStatuses?: ('All' | 'Published' | 'Unpublished' | 'Accepted')[];
187
+ /** Filter by specific locations (multiple) */
188
+ selectedLocations?: string[];
189
+ /** Filter by specific employees (multiple) */
190
+ selectedEmployees?: string[];
191
+ /** Filter by specific roles (multiple) */
192
+ selectedRoles?: string[];
193
+ /** Return only shifts without role assignments */
194
+ unassignedShiftsOnly?: boolean;
195
+ /** Exclude shifts overlapping the from date */
196
+ excludeShiftsOverlappingFromDate?: boolean;
197
+ /** Include warning data in response */
198
+ includeWarnings?: boolean;
199
+ }
200
+ /**
201
+ * Options for getKioskStaff
202
+ */
203
+ export interface EHKioskStaffOptions {
204
+ /** Restrict current shifts to current kiosk location (default: false) */
205
+ restrictCurrentShiftsToCurrentKioskLocation?: boolean;
206
+ }
207
+ /**
208
+ * Available field for the Employee Details Report
209
+ *
210
+ * From GET /business/{id}/report/employeedetails/fields
211
+ */
212
+ export interface EHReportField {
213
+ /** Field identifier (used in selectedColumns) */
214
+ value: string;
215
+ /** Human-readable field name */
216
+ displayText: string;
217
+ }
218
+ /**
219
+ * Options for getEmployeeDetailsReport
220
+ */
221
+ export interface EHEmployeeDetailsReportOptions {
222
+ /** Column names to include in the report */
223
+ selectedColumns?: string[];
224
+ /** Filter by location ID */
225
+ locationId?: number;
226
+ /** Filter by employing entity ID */
227
+ employingEntityId?: number;
228
+ /** Include active employees (default: true) */
229
+ includeActive?: boolean;
230
+ /** Include inactive employees (default: false) */
231
+ includeInactive?: boolean;
232
+ }
233
+ /**
234
+ * Structured error information from EH API
235
+ */
236
+ export interface EHErrorInfo {
237
+ /** Human-readable error message */
238
+ message: string;
239
+ /** HTTP status code from the response */
240
+ statusCode: number;
241
+ }
package/dist/types.js ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Employment Hero Payroll Type Definitions
3
+ *
4
+ * Types for the Employment Hero Payroll (KeyPay) API.
5
+ * Based on the API reference and KeyPay .NET SDK models.
6
+ */
7
+ export {};
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Employment Hero Payroll Utility Functions
3
+ */
4
+ /**
5
+ * Build the Authorization header for EH API requests (HTTP Basic Auth)
6
+ *
7
+ * EH API uses the API key as the username with an empty password.
8
+ */
9
+ export declare function buildBasicAuthHeader(apiKey: string): string;
package/dist/utils.js ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Employment Hero Payroll Utility Functions
3
+ */
4
+ // ============================================================================
5
+ // Authentication
6
+ // ============================================================================
7
+ /**
8
+ * Build the Authorization header for EH API requests (HTTP Basic Auth)
9
+ *
10
+ * EH API uses the API key as the username with an empty password.
11
+ */
12
+ export function buildBasicAuthHeader(apiKey) {
13
+ return 'Basic ' + btoa(apiKey + ':');
14
+ }
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@markwharton/eh-payroll",
3
+ "version": "1.0.0",
4
+ "description": "Employment Hero Payroll API client",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "clean": "rm -rf dist"
17
+ },
18
+ "dependencies": {
19
+ "@markwharton/api-core": "^1.0.0"
20
+ },
21
+ "devDependencies": {
22
+ "@types/node": "^20.10.0",
23
+ "typescript": "^5.3.0"
24
+ },
25
+ "files": [
26
+ "dist"
27
+ ],
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/MarkWharton/api-packages.git",
31
+ "directory": "packages/eh-payroll"
32
+ },
33
+ "publishConfig": {
34
+ "access": "public"
35
+ },
36
+ "author": "Mark Wharton",
37
+ "license": "MIT",
38
+ "engines": {
39
+ "node": ">=20"
40
+ }
41
+ }