core/selectors/rate.selector.ts
core/selectors/rate.selector.ts is a file in Project Scripts. 101 lines of code and 36 definitions.
import { COUNT_MARKER, RECORD_MARKER, UPDATE_MARKER } from "#configuration/constants/rate.constants";
import type { RateRecord } from "#types/rate.types";
import { isRecord } from "@banes-lab/build-scripts/core/selectors/base.selector.ts";
import { unreadableRecord } from "#configuration/strings/rate.strings";
const OPEN = "{";
const CLOSE = "}";
const QUOTE = '"';
const ESCAPE = "\\";
const TAG_END = ">";
const TAG_START = "<";
const DEPTH_STEP: ReadonlyMap<string, number> = new Map([
[OPEN, 1],
[CLOSE, -1],
]);
const objectEnd = function objectEnd(text: string, start: number): number {
let depth = 0;
let inString = false;
for (let at = start; at < text.length; at += 1) {
const char = text.charAt(at);
if (inString) {
at += char === ESCAPE ? 1 : 0;
inString = char !== QUOTE;
} else {
inString = char === QUOTE;
depth += DEPTH_STEP.get(char) ?? 0;
if (depth === 0 && char === CLOSE) {
return at;
}
}
}
return -1;
};
const stringsOf = function stringsOf(value: unknown, fallback: unknown): string[] {
const list: unknown[] = Array.isArray(value) ? value : [fallback];
return list.filter((item): item is string => typeof item === "string");
};
const numberOf = function numberOf(value: unknown): number | null {
return typeof value === "number" && Number.isFinite(value) ? value : null;
};
const rateOf = function rateOf(raw: Record<string, unknown>): number | null {
const exact = numberOf(raw["dailyRateEur"]);
if (exact !== null) {
return exact;
}
const low = numberOf(raw["dayRateMin"]);
const high = numberOf(raw["dayRateMax"]) ?? low;
return low === null || high === null ? null : (low + high) / 2;
};
const recordOf = function recordOf(raw: unknown): RateRecord | null {
if (!isRecord(raw)) {
return null;
}
const rate = rateOf(raw);
const id = raw["externalId"];
if (rate === null || typeof id !== "string") {
return null;
}
return {
country: typeof raw["country"] === "string" ? raw["country"] : "",
createdAt: numberOf(raw["createdAtUtc"]) ?? 0,
id,
rate,
seniorities: stringsOf(raw["seniorities"], raw["seniority"]),
titles: stringsOf(raw["jobTitles"], raw["jobTitle"]),
};
};
const parsedAt = function parsedAt(text: string, start: number, end: number): RateRecord | null {
try {
const parsed: unknown = JSON.parse(text.slice(start, end + 1));
return recordOf(parsed);
} catch (error: unknown) {
throw new Error(unreadableRecord(start), { cause: error });
}
};
export const recordsIn = function recordsIn(html: string): readonly RateRecord[] {
const byId = new Map<string, RateRecord>();
let start = html.indexOf(RECORD_MARKER);
while (start !== -1) {
const end = objectEnd(html, start);
const record = end === -1 ? null : parsedAt(html, start, end);
if (record !== null) {
byId.set(record.id, record);
}
start = end === -1 ? -1 : html.indexOf(RECORD_MARKER, end + 1);
}
return [...byId.values()];
};
const textAfter = function textAfter(html: string, marker: string): string {
const at = html.indexOf(marker);
const open = at === -1 ? -1 : html.indexOf(TAG_END, at);
const close = open === -1 ? -1 : html.indexOf(TAG_START, open);
return close === -1 ? "" : html.slice(open + 1, close).trim();
};
export const statedCount = function statedCount(html: string): number | null {
const text = textAfter(html, COUNT_MARKER);
const count = Number(text);
return text.length > 0 && Number.isInteger(count) ? count : null;
};
export const statedUpdate = function statedUpdate(html: string): string {
return textAfter(html, UPDATE_MARKER);
};