openings 0.1.64 → 0.1.65
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/package.json +1 -1
- package/src/catalog.ts +19 -4
- package/src/version.ts +1 -1
package/package.json
CHANGED
package/src/catalog.ts
CHANGED
|
@@ -258,14 +258,23 @@ async function fetchWorkdayJobs(company: Company, fetcher: Fetch, signal?: Abort
|
|
|
258
258
|
previousPageStart = pacingNow();
|
|
259
259
|
} finally { release(); }
|
|
260
260
|
}
|
|
261
|
-
async function page(offset: number, appliedFacets: Record<string, string[]> = {}): Promise<{ total: number; jobs: WorkdayJob[]; facets?: unknown }> {
|
|
261
|
+
async function page(offset: number, appliedFacets: Record<string, string[]> = {}): Promise<{ total: number; jobs: WorkdayJob[]; facets?: unknown; unusable?: number }> {
|
|
262
262
|
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
263
263
|
await pacePageStart();
|
|
264
264
|
const response = await fetcher(endpoint, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ appliedFacets, limit, offset, searchText: "" }), signal });
|
|
265
265
|
if (response.ok) {
|
|
266
266
|
const body = await response.json() as { total?: unknown; jobPostings?: unknown; facets?: unknown };
|
|
267
267
|
if (!Number.isInteger(body.total) || !Array.isArray(body.jobPostings)) throw new Error(`${company.name} Workday source returned an invalid payload`);
|
|
268
|
-
|
|
268
|
+
// Workday began returning postings with no externalPath on 13 October, and every one of them threw
|
|
269
|
+
// inside workdayJobKey, taking the whole tenant down with it: 48 sources failed in an afternoon. A
|
|
270
|
+
// posting with no path cannot produce an apply link, so it is of no use here either way — but it is
|
|
271
|
+
// dropped loudly, because a provider quietly shedding postings is how a source empties itself.
|
|
272
|
+
const postings = (body.jobPostings as WorkdayJob[]).filter((job) => typeof job?.externalPath === "string" && job.externalPath.length > 0);
|
|
273
|
+
if (postings.length < body.jobPostings.length) {
|
|
274
|
+
console.error(`${company.name} Workday source: skipped ${body.jobPostings.length - postings.length} posting(s) with no externalPath`);
|
|
275
|
+
}
|
|
276
|
+
return { total: body.total as number, jobs: postings, facets: body.facets,
|
|
277
|
+
unusable: (body.jobPostings as unknown[]).length - postings.length };
|
|
269
278
|
}
|
|
270
279
|
if (!isTransientStatus(response.status) || attempt === 2) { await response.body?.cancel().catch(() => undefined); throw new Error(`${company.name} job board returned HTTP ${response.status}`); }
|
|
271
280
|
const delayMs = retryDelayMs(response, attempt);
|
|
@@ -279,6 +288,7 @@ async function fetchWorkdayJobs(company: Company, fetcher: Fetch, signal?: Abort
|
|
|
279
288
|
if (first.total < first.jobs.length || first.total > 100_000) throw new Error(`${company.name} Workday source reported an invalid total`);
|
|
280
289
|
const offsets = Array.from({ length: Math.ceil(first.total / limit) - 1 }, (_, index) => (index + 1) * limit);
|
|
281
290
|
const pages = new Array<WorkdayJob[]>(offsets.length);
|
|
291
|
+
const pageUnusable: number[] = [];
|
|
282
292
|
let cursor = 0;
|
|
283
293
|
async function worker() {
|
|
284
294
|
while (cursor < offsets.length) {
|
|
@@ -287,12 +297,17 @@ async function fetchWorkdayJobs(company: Company, fetcher: Fetch, signal?: Abort
|
|
|
287
297
|
const result = await page(offset);
|
|
288
298
|
if (result.jobs.length === 0 && offset < first.total) throw new Error(`${company.name} Workday source truncated at offset ${offset} of ${first.total}`);
|
|
289
299
|
pages[index] = result.jobs;
|
|
300
|
+
pageUnusable[index] = result.unusable ?? 0;
|
|
290
301
|
}
|
|
291
302
|
}
|
|
292
303
|
const workerCount = pageDelayMs > 0 ? 1 : Math.min(4, offsets.length);
|
|
293
304
|
await Promise.all(Array.from({ length: workerCount }, worker));
|
|
294
|
-
|
|
295
|
-
|
|
305
|
+
// Postings Workday sent without a path were never usable, so they are not evidence of a truncated read.
|
|
306
|
+
// The check still fires for a genuinely short page, which is what stops a partial crawl emptying a tenant.
|
|
307
|
+
const unusable = (first.unusable ?? 0) + pages.reduce((sum, _, index) => sum + (pageUnusable[index] ?? 0), 0);
|
|
308
|
+
const expected = first.total - unusable;
|
|
309
|
+
const jobs = [first.jobs, ...pages].flat().slice(0, expected);
|
|
310
|
+
if (jobs.length !== expected) throw new Error(`${company.name} Workday source returned ${jobs.length} of ${expected} jobs`);
|
|
296
311
|
// Two reasons to run a country pass with the tenant's own filter: past 2,000 postings the unfiltered listing stops,
|
|
297
312
|
// and multi-location postings read "2 Locations" there, so their countries are invisible without the filter.
|
|
298
313
|
const countryFacets = workdayCountryFacets(first.facets);
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = "0.1.
|
|
1
|
+
export const VERSION = "0.1.65";
|