@markwharton/eh-payroll 2.1.2 → 2.2.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
@@ -131,6 +131,7 @@ const client = new EHClient({
131
131
  rateLimitPerSecond: 5, // Optional: rate limit (default 5, set 0 to disable)
132
132
  onRequest: ({ method, url, description }) => { ... }, // Optional: debug callback
133
133
  cache: {}, // Optional: enable TTL caching (defaults below)
134
+ cacheInstance: cache, // Optional: custom cache backend (e.g., LayeredCache)
134
135
  retry: { // Optional: retry on 429/503
135
136
  maxRetries: 3,
136
137
  initialDelayMs: 1000,
@@ -139,7 +140,7 @@ const client = new EHClient({
139
140
  });
140
141
  ```
141
142
 
142
- `EHConfig` extends `ClientConfig` from api-core, which provides the `baseUrl`, `onRequest`, and `retry` fields. `apiKey`, `businessId`, `region`, `cache`, and `rateLimitPerSecond` are EH-specific.
143
+ `EHConfig` extends `ClientConfig` from api-core, which provides the `baseUrl`, `onRequest`, `retry`, and `cacheInstance` fields. `apiKey`, `businessId`, `region`, `cache`, and `rateLimitPerSecond` are EH-specific. Pass `cacheInstance` to use a custom cache backend (e.g., `LayeredCache` with persistent stores); otherwise `cache: {}` creates an in-memory `TTLCache`. PII data (`includePii: true`) automatically skips persistent stores.
143
144
 
144
145
  ### Cache TTLs
145
146
 
@@ -154,6 +155,8 @@ const client = new EHClient({
154
155
  | Kiosk staff | 1 min |
155
156
  | Report fields | 10 min |
156
157
 
158
+ Failed API results (`ok: false`) are never cached — transient errors won't persist for the full TTL. See the [root README Cache System section](../../README.md#cache-system) for the full cache architecture (layered stores, PII handling, request coalescing).
159
+
157
160
  ### Rate Limiting
158
161
 
159
162
  The client enforces a sliding-window rate limit of 5 requests per second (the [API limit](https://api.keypay.com.au/australia/guides/Usage.html)). All outbound requests pass through the rate limiter automatically. Set `rateLimitPerSecond: 0` in config to disable (useful for tests). The `RateLimiter` class is provided by api-core and operates per-instance (see Known Limitations in the root README).
package/dist/client.d.ts CHANGED
@@ -35,6 +35,8 @@ export declare class EHClient {
35
35
  constructor(config: EHConfig);
36
36
  /**
37
37
  * Route through cache if enabled, otherwise call factory directly.
38
+ * Failed Results (ok === false) are never cached — transient errors
39
+ * shouldn't persist for the full TTL.
38
40
  */
39
41
  private cached;
40
42
  /**
package/dist/client.js CHANGED
@@ -37,8 +37,8 @@ export class EHClient {
37
37
  this.baseUrl = config.baseUrl ?? (config.region ? EH_REGION_URLS[config.region] : EH_API_BASE);
38
38
  this.onRequest = config.onRequest;
39
39
  // Initialize cache if configured
40
- if (config.cache) {
41
- this.cache = new TTLCache();
40
+ if (config.cache || config.cacheInstance) {
41
+ this.cache = config.cacheInstance ?? new TTLCache();
42
42
  }
43
43
  this.cacheTtl = {
44
44
  employeesTtl: config.cache?.employeesTtl ?? 300000,
@@ -60,12 +60,16 @@ export class EHClient {
60
60
  }
61
61
  /**
62
62
  * Route through cache if enabled, otherwise call factory directly.
63
+ * Failed Results (ok === false) are never cached — transient errors
64
+ * shouldn't persist for the full TTL.
63
65
  */
64
- async cached(key, ttlMs, factory) {
65
- if (this.cache) {
66
- return this.cache.get(key, ttlMs, factory);
67
- }
68
- return factory();
66
+ async cached(key, ttlMs, factory, options) {
67
+ if (!this.cache)
68
+ return factory();
69
+ return this.cache.get(key, ttlMs, factory, {
70
+ ...options,
71
+ shouldCache: (data) => data.ok !== false,
72
+ });
69
73
  }
70
74
  /**
71
75
  * Clear all cached API responses.
@@ -197,6 +201,7 @@ export class EHClient {
197
201
  if (options?.includePii)
198
202
  parts.push('pii');
199
203
  const cacheKey = parts.join(':');
204
+ const persistOpt = options?.includePii ? { persist: false } : undefined;
200
205
  return this.cached(cacheKey, this.cacheTtl.employeesTtl, () => {
201
206
  const params = new URLSearchParams();
202
207
  if (options?.payScheduleId != null)
@@ -208,7 +213,7 @@ export class EHClient {
208
213
  params.set('$skip', String(skip));
209
214
  return `${this.baseUrl}/business/${this.businessId}/employee/unstructured?${params}`;
210
215
  }, fields);
211
- });
216
+ }, persistOpt);
212
217
  }
213
218
  /**
214
219
  * Get a single employee by ID
@@ -216,12 +221,13 @@ export class EHClient {
216
221
  async getEmployee(employeeId, options) {
217
222
  const fields = options?.includePii ? AU_EMPLOYEE_FIELDS : AU_EMPLOYEE_OPERATIONAL_FIELDS;
218
223
  const cacheKey = options?.includePii ? `employee:${employeeId}:pii` : `employee:${employeeId}`;
224
+ const persistOpt = options?.includePii ? { persist: false } : undefined;
219
225
  return this.cached(cacheKey, this.cacheTtl.employeesTtl, async () => {
220
226
  const url = `${this.baseUrl}/business/${this.businessId}/employee/unstructured/${employeeId}`;
221
227
  return this.fetchAndParse(url, async (r) => {
222
228
  return pickFields(await r.json(), fields);
223
229
  });
224
- });
230
+ }, persistOpt);
225
231
  }
226
232
  // ============================================================================
227
233
  // Standard Hours
package/dist/index.d.ts CHANGED
@@ -24,8 +24,8 @@ export type { EHConfig, EHCacheConfig, EHRetryConfig, EHEmployee, EHEmployeeOpti
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 { buildBasicAuthHeader } from './utils.js';
27
- export { ok, err, getErrorMessage, pickFields, RateLimiter } from '@markwharton/api-core';
28
- export type { Result, RetryConfig, OnRequestCallback, ClientConfig } from '@markwharton/api-core';
27
+ export { ok, err, getErrorMessage, pickFields, RateLimiter, TTLCache, MemoryCacheStore, LayeredCache } from '@markwharton/api-core';
28
+ export type { Result, RetryConfig, OnRequestCallback, ClientConfig, Cache, CacheStore, CacheGetOptions } from '@markwharton/api-core';
29
29
  export { EH_API_BASE, EH_REGION_URLS } from './constants.js';
30
30
  export type { EHRegion } from './constants.js';
31
31
  export { EHPayrollError, parseEHPayrollErrorResponse } from './errors.js';
package/dist/index.js CHANGED
@@ -26,7 +26,7 @@ export { AU_EMPLOYEE_OPERATIONAL_FIELDS, AU_EMPLOYEE_PII_FIELDS, AU_EMPLOYEE_FIE
26
26
  // Utilities
27
27
  export { buildBasicAuthHeader } from './utils.js';
28
28
  // Re-exported from @markwharton/api-core
29
- export { ok, err, getErrorMessage, pickFields, RateLimiter } from '@markwharton/api-core';
29
+ export { ok, err, getErrorMessage, pickFields, RateLimiter, TTLCache, MemoryCacheStore, LayeredCache } from '@markwharton/api-core';
30
30
  // Constants
31
31
  export { EH_API_BASE, EH_REGION_URLS } from './constants.js';
32
32
  // Errors
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markwharton/eh-payroll",
3
- "version": "2.1.2",
3
+ "version": "2.2.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.2.0"
19
+ "@markwharton/api-core": "^1.3.0"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@types/node": "^20.10.0",