Migrate remaining legacy operator workflows

This commit is contained in:
2026-07-12 05:39:27 +09:00
parent ffb8f43c19
commit a01836a2d7
132 changed files with 20566 additions and 720 deletions

View File

@@ -13,6 +13,9 @@
const MAXIMUM_PAGE_COUNT = 20;
const MAXIMUM_STORED_PAGE_COUNT = 9999;
const DEFAULT_MAXIMUM_DEFINITIONS = 200;
const MAXIMUM_SELECTION_FIELD_LENGTH = 256;
const MAXIMUM_SUBJECT_FIELD_LENGTH = 4000;
const MAXIMUM_LIST_TEXT_LENGTH = 4000;
const identifierPattern = /^[A-Za-z0-9_-]{1,128}$/;
const programCodePattern = /^[0-9]{8}$/;
const cutCodePattern = /^[0-9]{1,8}$/;
@@ -145,11 +148,11 @@
if (!hasExactKeys(value, ["groupCode", "subject", "graphicType", "subtype", "dataCode"])) {
return null;
}
const groupCode = exactText(value.groupCode, 256, true);
const subject = exactText(value.subject, 256, true);
const graphicType = exactText(value.graphicType, 256, true);
const subtype = exactText(value.subtype, 256, true);
const dataCode = exactText(value.dataCode, 256, true);
const groupCode = exactText(value.groupCode, MAXIMUM_SELECTION_FIELD_LENGTH, true);
const subject = exactText(value.subject, MAXIMUM_SUBJECT_FIELD_LENGTH, true);
const graphicType = exactText(value.graphicType, MAXIMUM_SELECTION_FIELD_LENGTH, true);
const subtype = exactText(value.subtype, MAXIMUM_SELECTION_FIELD_LENGTH, true);
const dataCode = exactText(value.dataCode, MAXIMUM_SELECTION_FIELD_LENGTH, true);
if ([groupCode, subject, graphicType, subtype, dataCode].some(field => field === null)) return null;
return Object.freeze({ groupCode, subject, graphicType, subtype, dataCode });
}
@@ -184,13 +187,18 @@
fieldsValue.some(field => typeof field !== "string" || field.includes("^") ||
controlCharacterPattern.test(field)) ||
!["0", "1"].includes(fieldsValue[0]) ||
fieldsValue.slice(1, 5).some(field => exactText(field, 256, true) === null) ||
exactText(fieldsValue[6], 256, true) === null ||
exactText(fieldsValue[1], MAXIMUM_SELECTION_FIELD_LENGTH, true) === null ||
exactText(fieldsValue[2], MAXIMUM_SUBJECT_FIELD_LENGTH, true) === null ||
exactText(fieldsValue[3], MAXIMUM_SELECTION_FIELD_LENGTH, true) === null ||
exactText(fieldsValue[4], MAXIMUM_SELECTION_FIELD_LENGTH, true) === null ||
exactText(fieldsValue[6], MAXIMUM_SELECTION_FIELD_LENGTH, true) === null ||
(fieldsValue[5] !== "" && !parseStoredPageText(fieldsValue[5]))) {
throw new TypeError("Exactly seven safe legacy playlist fields are required.");
}
const value = fieldsValue.join("^");
if (value.length > 4000) throw new RangeError("The legacy LIST_TEXT value is too long.");
if (value.length > MAXIMUM_LIST_TEXT_LENGTH) {
throw new RangeError("The legacy LIST_TEXT value is too long.");
}
return value;
}
@@ -209,6 +217,9 @@
if (options.pageTextForEntry !== undefined && typeof options.pageTextForEntry !== "function") {
throw new TypeError("pageTextForEntry must be a function when supplied.");
}
if (options.fieldsForEntry !== undefined && typeof options.fieldsForEntry !== "function") {
throw new TypeError("fieldsForEntry must be a function when supplied.");
}
const items = playlistEntries.map((entry, itemIndex) => {
if (options.isTrustedEntry(entry, itemIndex) !== true) {
@@ -217,7 +228,16 @@
const pageText = options.pageTextForEntry
? options.pageTextForEntry(entry, itemIndex)
: "1/1";
const fields = toLegacySevenFields(entry, pageText);
const projected = options.fieldsForEntry
? options.fieldsForEntry(entry, itemIndex, pageText)
: undefined;
const fields = options.fieldsForEntry
? projected
: toLegacySevenFields(entry, pageText);
if (!Array.isArray(fields) || fields.length !== 7 ||
fields[0] !== (entry.enabled ? "1" : "0") || fields[5] !== pageText) {
throw new TypeError(`Playlist entry ${itemIndex} has no exact legacy seven-field projection.`);
}
legacyListText(fields);
return Object.freeze({
itemIndex,
@@ -367,18 +387,29 @@
return Object.freeze(copy);
}
function trustedEntryMatchesRaw(entry, rawRow) {
function compatibleSelection(entry, rawRow, compatibilityVerifier) {
if (sameSelection(entry, rawRow)) return true;
if (typeof compatibilityVerifier !== "function") return false;
try {
return compatibilityVerifier(entry, rawRow) === true;
} catch {
return false;
}
}
function trustedEntryMatchesRaw(entry, rawRow, compatibilityVerifier) {
return isPlainObject(entry) && requestId(entry.id) &&
typeof entry.builderKey === "string" && builderKeyPattern.test(entry.builderKey) &&
typeof entry.code === "string" && cutCodePattern.test(entry.code) &&
entry.enabled === rawRow.enabled && sameSelection(entry, rawRow);
entry.enabled === rawRow.enabled && compatibleSelection(entry, rawRow, compatibilityVerifier);
}
function restoreRawRows(loadValue, trustedResolver) {
function restoreRawRows(loadValue, trustedResolver, compatibilityVerifier) {
const load = loadValue?.kind === "named-playlist-load"
? loadValue
: normalizeLoadResponse(loadValue);
if (!load || typeof trustedResolver !== "function") {
if (!load || typeof trustedResolver !== "function" ||
(compatibilityVerifier !== undefined && typeof compatibilityVerifier !== "function")) {
throw new TypeError("A normalized load response and trusted resolver are required.");
}
const rows = load.rawRows.map(rawRow => {
@@ -388,7 +419,7 @@
} catch {
candidate = null;
}
const trusted = trustedEntryMatchesRaw(candidate, rawRow);
const trusted = trustedEntryMatchesRaw(candidate, rawRow, compatibilityVerifier);
return Object.freeze({
rawRow,
entry: trusted ? freezeTrustedEntry(candidate) : null,