(function (root, factory) { "use strict"; const api = Object.freeze(factory()); if (typeof module === "object" && module.exports) module.exports = api; if (root) root.MbnOperatorCatalogWorkflow = api; })(typeof globalThis === "object" ? globalThis : this, function () { "use strict"; const MAXIMUM_THEME_ITEMS = 240; const MAXIMUM_RECOMMENDATIONS = 100; const MAXIMUM_RESULTS = 500; const MAXIMUM_QUERY_LENGTH = 64; const MAXIMUM_WEB_SAFE_INTEGER = Number.MAX_SAFE_INTEGER; const requestIdPattern = /^[A-Za-z0-9_-]{1,128}$/; const themeCodePattern = /^[0-9]{8}$/; const expertCodePattern = /^[0-9]{4}$/; const stockCodePattern = /^[A-Za-z0-9]{1,32}$/; const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; const unsafeTextPattern = /[\p{Cc}\p{Cf}\p{Cs}\p{Zl}\p{Zp}\uFFFD]/u; const bridgeContract = Object.freeze({ requests: Object.freeze({ themeList: "request-theme-catalog-list", expertList: "request-expert-catalog-list", schemaPreflight: "request-operator-catalog-schema", themeNextCode: "request-theme-catalog-next-code", expertNextCode: "request-expert-catalog-next-code", themeCreate: "create-theme-catalog", themeReplace: "replace-theme-catalog", themeDelete: "delete-theme-catalog", expertCreate: "create-expert-catalog", expertReplace: "replace-expert-catalog", expertDelete: "delete-expert-catalog" }), events: Object.freeze({ themeList: "theme-catalog-list-results", expertList: "expert-catalog-list-results", schemaPreflight: "operator-catalog-schema-results", themeNextCode: "theme-catalog-next-code-results", expertNextCode: "expert-catalog-next-code-results", mutation: "operator-catalog-mutation-results", error: "operator-catalog-error" }) }); function hasExactKeys(value, keys) { if (!value || typeof value !== "object" || Array.isArray(value)) return false; const actual = Object.keys(value).sort(); const expected = [...keys].sort(); return actual.length === expected.length && actual.every((key, index) => key === expected[index]); } function hasOnlyKeys(value, keys) { return value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).every(key => keys.includes(key)); } function isSafeCanonicalText(value, maximumLength, allowEmpty = false) { return typeof value === "string" && (allowEmpty ? value.length >= 0 : value.length > 0) && value.length <= maximumLength && value === value.trim() && !unsafeTextPattern.test(value); } function requireRequestId(value) { if (typeof value !== "string" || !requestIdPattern.test(value)) { throw new TypeError("A safe operator-catalog request id is required."); } return value; } function normalizeQuery(value) { if (typeof value !== "string" || unsafeTextPattern.test(value)) return null; const query = value.trim(); return query.length <= MAXIMUM_QUERY_LENGTH && !unsafeTextPattern.test(query) ? query : null; } function normalizeMarket(value) { return value === "krx" || value === "nxt" ? value : null; } function normalizeSession(value, market) { if (market === "krx") return value === "notApplicable" ? value : null; if (market === "nxt") { return value === "preMarket" || value === "afterMarket" ? value : null; } return null; } function normalizeThemeIdentity(value) { if (!hasExactKeys(value, ["market", "session", "themeCode", "themeTitle"])) return null; const market = normalizeMarket(value.market); if (!market || !normalizeSession(value.session, market) || !themeCodePattern.test(value.themeCode) || !isSafeCanonicalText(value.themeTitle, 128) || (market === "nxt" && /\(NXT\)/i.test(value.themeTitle))) return null; return Object.freeze({ market, session: value.session, themeCode: value.themeCode, themeTitle: value.themeTitle }); } function normalizeThemeItem(value, expectedIndex, market) { if (!hasExactKeys(value, ["inputIndex", "itemCode", "itemName"]) || value.inputIndex !== expectedIndex || !Number.isInteger(value.inputIndex) || !isSafeCanonicalText(value.itemCode, 13) || !isSafeCanonicalText(value.itemName, 200)) return null; const prefix = value.itemCode[0]; const suffix = value.itemCode.slice(1); if (!/^[A-Za-z0-9]{1,12}$/.test(suffix) || (market === "krx" && prefix !== "P" && prefix !== "D") || (market === "nxt" && prefix !== "X" && prefix !== "T")) return null; return Object.freeze({ inputIndex: value.inputIndex, itemCode: value.itemCode, itemName: value.itemName }); } function normalizeThemeItems(value, market) { if (!Array.isArray(value) || value.length > MAXIMUM_THEME_ITEMS) return null; const items = []; const codes = new Set(); const names = new Set(); for (let index = 0; index < value.length; index += 1) { const item = normalizeThemeItem(value[index], index, market); if (!item || codes.has(item.itemCode) || names.has(item.itemName)) return null; codes.add(item.itemCode); names.add(item.itemName); items.push(item); } return Object.freeze(items); } function normalizeThemeDraft(value) { if (!hasExactKeys(value, ["market", "themeCode", "themeTitle", "items"])) return null; const market = normalizeMarket(value.market); if (!market || !themeCodePattern.test(value.themeCode) || !isSafeCanonicalText(value.themeTitle, 128) || (market === "nxt" && /\(NXT\)/i.test(value.themeTitle))) return null; const items = normalizeThemeItems(value.items, market); return items && Object.freeze({ market, themeCode: value.themeCode, themeTitle: value.themeTitle, items }); } function normalizeExpertIdentity(value) { if (!hasExactKeys(value, ["expertCode", "expertName"]) || !expertCodePattern.test(value.expertCode) || !isSafeCanonicalText(value.expertName, 128)) return null; return Object.freeze({ expertCode: value.expertCode, expertName: value.expertName }); } function normalizeRecommendation(value, expectedIndex) { if (!hasExactKeys(value, ["playIndex", "stockCode", "stockName", "buyAmount"]) || value.playIndex !== expectedIndex || !Number.isInteger(value.playIndex) || !stockCodePattern.test(value.stockCode) || !isSafeCanonicalText(value.stockName, 200) || !Number.isSafeInteger(value.buyAmount) || value.buyAmount <= 0 || value.buyAmount > MAXIMUM_WEB_SAFE_INTEGER) return null; return Object.freeze({ playIndex: value.playIndex, stockCode: value.stockCode, stockName: value.stockName, buyAmount: value.buyAmount }); } function normalizeRecommendations(value) { if (!Array.isArray(value) || value.length > MAXIMUM_RECOMMENDATIONS) return null; const items = []; const codes = new Set(); const names = new Set(); for (let index = 0; index < value.length; index += 1) { const item = normalizeRecommendation(value[index], index); if (!item || codes.has(item.stockCode) || names.has(item.stockName)) return null; codes.add(item.stockCode); names.add(item.stockName); items.push(item); } return Object.freeze(items); } function normalizeExpertDraft(value) { if (!hasExactKeys(value, ["expertCode", "expertName", "recommendations"])) return null; const identity = normalizeExpertIdentity({ expertCode: value.expertCode, expertName: value.expertName }); const recommendations = normalizeRecommendations(value.recommendations); return identity && recommendations && Object.freeze({ expertCode: identity.expertCode, expertName: identity.expertName, recommendations }); } function createThemeCatalogListRequest(requestId, query = "", nxtSession = "preMarket", maximumResults) { requireRequestId(requestId); const normalizedQuery = normalizeQuery(query); if (normalizedQuery === null) throw new TypeError("A safe theme catalog query is required."); if (nxtSession !== "preMarket" && nxtSession !== "afterMarket") { throw new TypeError("An explicit NXT catalog session is required."); } if (maximumResults !== undefined && (!Number.isInteger(maximumResults) || maximumResults < 1 || maximumResults > MAXIMUM_RESULTS)) { throw new RangeError("Theme catalog results must be limited from 1 through 500."); } const request = { requestId, query: normalizedQuery, nxtSession }; if (maximumResults !== undefined) request.maximumResults = maximumResults; return Object.freeze(request); } function createExpertCatalogListRequest(requestId, query = "", maximumResults) { requireRequestId(requestId); const normalizedQuery = normalizeQuery(query); if (normalizedQuery === null) throw new TypeError("A safe expert catalog query is required."); if (maximumResults !== undefined && (!Number.isInteger(maximumResults) || maximumResults < 1 || maximumResults > MAXIMUM_RESULTS)) { throw new RangeError("Expert catalog results must be limited from 1 through 500."); } const request = { requestId, query: normalizedQuery }; if (maximumResults !== undefined) request.maximumResults = maximumResults; return Object.freeze(request); } function createSchemaPreflightRequest(requestId) { return Object.freeze({ requestId: requireRequestId(requestId) }); } function createThemeNextCodeRequest(requestId, market) { requireRequestId(requestId); if (!normalizeMarket(market)) throw new TypeError("A KRX or NXT market is required."); return Object.freeze({ requestId, market }); } function createExpertNextCodeRequest(requestId) { return Object.freeze({ requestId: requireRequestId(requestId) }); } function normalizeThemeCatalogSelection(value) { if (!hasExactKeys(value, ["market", "session", "themeCode", "themeTitle", "source"])) return null; const identity = normalizeThemeIdentity({ market: value.market, session: value.session, themeCode: value.themeCode, themeTitle: value.themeTitle }); if (!identity || (identity.market === "krx" && value.source !== "oracle") || (identity.market === "nxt" && value.source !== "mariaDb")) return null; return Object.freeze({ ...identity, source: value.source }); } function normalizeExpertCatalogSelection(value) { if (!hasExactKeys(value, ["expertCode", "expertName", "source"]) || value.source !== "oracle") { return null; } const identity = normalizeExpertIdentity({ expertCode: value.expertCode, expertName: value.expertName }); return identity && Object.freeze({ ...identity, source: "oracle" }); } function normalizeThemePreviewForEdit(value, selection) { if (!hasExactKeys(value, [ "requestId", "selection", "retrievedAt", "totalRowCount", "truncated", "items" ]) || !requestIdPattern.test(value.requestId) || !isSafeCanonicalText(value.retrievedAt, 64) || !Number.isFinite(Date.parse(value.retrievedAt)) || !Number.isInteger(value.totalRowCount) || value.totalRowCount < 0 || value.totalRowCount > MAXIMUM_THEME_ITEMS || value.truncated !== false || !hasExactKeys(value.selection, ["market", "session", "themeCode", "title"])) return null; const previewIdentity = normalizeThemeIdentity({ market: value.selection.market, session: value.selection.session, themeCode: value.selection.themeCode, themeTitle: value.selection.title }); if (!previewIdentity || ["market", "session", "themeCode", "themeTitle"] .some(key => previewIdentity[key] !== selection[key])) return null; if (!Array.isArray(value.items) || value.items.length !== value.totalRowCount) return null; const items = []; const codes = new Set(); const names = new Set(); let previousIndex = -1; for (const raw of value.items) { if (!hasExactKeys(raw, ["inputIndex", "itemCode", "itemName"]) || !Number.isInteger(raw.inputIndex) || raw.inputIndex <= previousIndex || raw.inputIndex > 9999 || !isSafeCanonicalText(raw.itemCode, 13) || !isSafeCanonicalText(raw.itemName, 200)) return null; const prefix = raw.itemCode[0]; const suffix = raw.itemCode.slice(1); if (!/^[A-Za-z0-9]{1,12}$/.test(suffix) || (selection.market === "krx" && prefix !== "P" && prefix !== "D") || (selection.market === "nxt" && prefix !== "X" && prefix !== "T") || codes.has(raw.itemCode) || names.has(raw.itemName)) return null; previousIndex = raw.inputIndex; codes.add(raw.itemCode); names.add(raw.itemName); items.push(Object.freeze({ inputIndex: items.length, itemCode: raw.itemCode, itemName: raw.itemName })); } return Object.freeze(items); } function normalizeExpertPreviewForEdit(value, selection) { if (!hasExactKeys(value, [ "requestId", "selection", "retrievedAt", "totalRowCount", "truncated", "items" ]) || !requestIdPattern.test(value.requestId) || !isSafeCanonicalText(value.retrievedAt, 64) || !Number.isFinite(Date.parse(value.retrievedAt)) || !Number.isInteger(value.totalRowCount) || value.totalRowCount < 0 || value.totalRowCount > MAXIMUM_RECOMMENDATIONS || value.truncated !== false) return null; const previewIdentity = normalizeExpertIdentity(value.selection); if (!previewIdentity || previewIdentity.expertCode !== selection.expertCode || previewIdentity.expertName !== selection.expertName) return null; if (!Array.isArray(value.items) || value.items.length !== value.totalRowCount) return null; const items = []; const codes = new Set(); const names = new Set(); let previousIndex = -1; for (const raw of value.items) { if (!hasExactKeys(raw, ["playIndex", "stockCode", "stockName", "buyAmount"]) || !Number.isInteger(raw.playIndex) || raw.playIndex <= previousIndex || raw.playIndex > 9999 || !stockCodePattern.test(raw.stockCode) || !isSafeCanonicalText(raw.stockName, 200) || !Number.isSafeInteger(raw.buyAmount) || raw.buyAmount <= 0 || codes.has(raw.stockCode) || names.has(raw.stockName)) return null; previousIndex = raw.playIndex; codes.add(raw.stockCode); names.add(raw.stockName); items.push(Object.freeze({ playIndex: items.length, stockCode: raw.stockCode, stockName: raw.stockName, buyAmount: raw.buyAmount })); } return Object.freeze(items); } function createThemeEditDraft(selectionValue, previewValue) { const selection = normalizeThemeCatalogSelection(selectionValue); if (!selection) throw new TypeError("A typed theme catalog selection is required."); const items = normalizeThemePreviewForEdit(previewValue, selection); if (!items) throw new TypeError("A complete, correlated theme preview is required for editing."); return { expectedIdentity: { market: selection.market, session: selection.session, themeCode: selection.themeCode, themeTitle: selection.themeTitle }, draft: { market: selection.market, themeCode: selection.themeCode, themeTitle: selection.themeTitle, items: items.map(item => ({ ...item })) } }; } function createExpertEditDraft(selectionValue, previewValue) { const selection = normalizeExpertCatalogSelection(selectionValue); if (!selection) throw new TypeError("A typed expert catalog selection is required."); const recommendations = normalizeExpertPreviewForEdit(previewValue, selection); if (!recommendations) throw new TypeError("A complete, correlated expert preview is required for editing."); return { expectedIdentity: { expertCode: selection.expertCode, expertName: selection.expertName }, draft: { expertCode: selection.expertCode, expertName: selection.expertName, recommendations: recommendations.map(item => ({ ...item })) } }; } function createBlankThemeDraft(market, themeCode) { if (!normalizeMarket(market) || !themeCodePattern.test(themeCode)) { throw new TypeError("A typed market and suggested eight-digit theme code are required."); } return { market, themeCode, themeTitle: "", items: [] }; } function createBlankExpertDraft(expertCode) { if (!expertCodePattern.test(expertCode)) { throw new TypeError("A suggested four-digit expert code is required."); } return { expertCode, expertName: "", recommendations: [] }; } function cloneThemeDraft(draft) { const normalized = normalizeThemeDraft(draft); return normalized && { market: normalized.market, themeCode: normalized.themeCode, themeTitle: normalized.themeTitle, items: normalized.items.map(item => ({ ...item })) }; } function cloneExpertDraft(draft) { const normalized = normalizeExpertDraft(draft); return normalized && { expertCode: normalized.expertCode, expertName: normalized.expertName, recommendations: normalized.recommendations.map(item => ({ ...item })) }; } function createThemeCreateRequest(requestId, draftValue) { requireRequestId(requestId); const draft = cloneThemeDraft(draftValue); if (!draft) throw new TypeError("A complete theme draft is required."); return Object.freeze({ requestId, draft }); } function createThemeReplaceRequest(requestId, editValue) { requireRequestId(requestId); if (!hasExactKeys(editValue, ["expectedIdentity", "draft"])) { throw new TypeError("An explicit theme edit identity and draft are required."); } const identity = normalizeThemeIdentity(editValue.expectedIdentity); const draft = cloneThemeDraft(editValue.draft); if (!identity || !draft || identity.market !== draft.market || identity.themeCode !== draft.themeCode) { throw new TypeError("The theme edit identity does not match its draft."); } return Object.freeze({ requestId, expectedIdentity: { ...identity }, newTitle: draft.themeTitle, items: draft.items }); } function createThemeDeleteRequest(requestId, identityValue) { requireRequestId(requestId); const identity = normalizeThemeIdentity(identityValue); if (!identity) throw new TypeError("An explicit theme identity is required."); return Object.freeze({ requestId, identity: { ...identity } }); } function createExpertCreateRequest(requestId, draftValue) { requireRequestId(requestId); const draft = cloneExpertDraft(draftValue); if (!draft) throw new TypeError("A complete expert draft is required."); return Object.freeze({ requestId, draft }); } function createExpertReplaceRequest(requestId, editValue) { requireRequestId(requestId); if (!hasExactKeys(editValue, ["expectedIdentity", "draft"])) { throw new TypeError("An explicit expert edit identity and draft are required."); } const identity = normalizeExpertIdentity(editValue.expectedIdentity); const draft = cloneExpertDraft(editValue.draft); if (!identity || !draft || identity.expertCode !== draft.expertCode) { throw new TypeError("The expert edit identity does not match its draft."); } return Object.freeze({ requestId, expectedIdentity: { ...identity }, newName: draft.expertName, recommendations: draft.recommendations }); } function createExpertDeleteRequest(requestId, identityValue) { requireRequestId(requestId); const identity = normalizeExpertIdentity(identityValue); if (!identity) throw new TypeError("An explicit expert identity is required."); return Object.freeze({ requestId, identity: { ...identity } }); } function normalizeThemeListResult(value, expectedRequest) { if (!hasExactKeys(value, [ "requestId", "query", "nxtSession", "retrievedAt", "totalRowCount", "truncated", "canMutate", "writeQuarantined", "results" ]) || !requestIdPattern.test(value.requestId) || normalizeQuery(value.query) !== value.query || (value.nxtSession !== "preMarket" && value.nxtSession !== "afterMarket") || !isSafeCanonicalText(value.retrievedAt, 64) || !Number.isFinite(Date.parse(value.retrievedAt)) || !Number.isInteger(value.totalRowCount) || value.totalRowCount < 0 || value.totalRowCount > MAXIMUM_RESULTS || typeof value.truncated !== "boolean" || typeof value.canMutate !== "boolean" || typeof value.writeQuarantined !== "boolean" || (value.writeQuarantined && value.canMutate) || !Array.isArray(value.results) || value.results.length !== value.totalRowCount) return null; if (expectedRequest && (value.requestId !== expectedRequest.requestId || value.query !== expectedRequest.query || value.nxtSession !== expectedRequest.nxtSession || value.results.length > (expectedRequest.maximumResults ?? 100))) return null; const results = []; const identities = new Set(); const titles = new Set(); for (const raw of value.results) { const item = normalizeThemeCatalogSelection(raw); const key = item && `${item.market}:${item.themeCode}`; const titleKey = item && `${item.market}:${item.themeTitle}`; if (!item || identities.has(key) || titles.has(titleKey)) return null; identities.add(key); titles.add(titleKey); results.push(item); } return Object.freeze({ ...value, results: Object.freeze(results) }); } function normalizeExpertListResult(value, expectedRequest) { if (!hasExactKeys(value, [ "requestId", "query", "retrievedAt", "totalRowCount", "truncated", "canMutate", "writeQuarantined", "results" ]) || !requestIdPattern.test(value.requestId) || normalizeQuery(value.query) !== value.query || !isSafeCanonicalText(value.retrievedAt, 64) || !Number.isFinite(Date.parse(value.retrievedAt)) || !Number.isInteger(value.totalRowCount) || value.totalRowCount < 0 || value.totalRowCount > MAXIMUM_RESULTS || typeof value.truncated !== "boolean" || typeof value.canMutate !== "boolean" || typeof value.writeQuarantined !== "boolean" || (value.writeQuarantined && value.canMutate) || !Array.isArray(value.results) || value.results.length !== value.totalRowCount) return null; if (expectedRequest && (value.requestId !== expectedRequest.requestId || value.query !== expectedRequest.query || value.results.length > (expectedRequest.maximumResults ?? 100))) return null; const results = []; const codes = new Set(); const names = new Set(); for (const raw of value.results) { const item = normalizeExpertCatalogSelection(raw); if (!item || codes.has(item.expertCode) || names.has(item.expertName)) return null; codes.add(item.expertCode); names.add(item.expertName); results.push(item); } return Object.freeze({ ...value, results: Object.freeze(results) }); } function normalizeThemeNextCodeResult(value, expectedRequest) { if (!hasExactKeys(value, [ "requestId", "market", "themeCode", "canMutate", "writeQuarantined" ]) || !requestIdPattern.test(value.requestId) || !normalizeMarket(value.market) || !themeCodePattern.test(value.themeCode) || typeof value.canMutate !== "boolean" || typeof value.writeQuarantined !== "boolean" || (value.writeQuarantined && value.canMutate)) return null; if (expectedRequest && (value.requestId !== expectedRequest.requestId || value.market !== expectedRequest.market)) return null; return Object.freeze({ ...value }); } function normalizeExpertNextCodeResult(value, expectedRequest) { if (!hasExactKeys(value, [ "requestId", "expertCode", "canMutate", "writeQuarantined" ]) || !requestIdPattern.test(value.requestId) || !expertCodePattern.test(value.expertCode) || typeof value.canMutate !== "boolean" || typeof value.writeQuarantined !== "boolean" || (value.writeQuarantined && value.canMutate)) return null; if (expectedRequest && value.requestId !== expectedRequest.requestId) return null; return Object.freeze({ ...value }); } function normalizeSchemaResult(value, expectedRequest) { if (!hasExactKeys(value, [ "requestId", "validatedAt", "validatedSources", "themeCanMutate", "expertCanMutate", "writeQuarantined" ]) || !requestIdPattern.test(value.requestId) || !isSafeCanonicalText(value.validatedAt, 64) || !Number.isFinite(Date.parse(value.validatedAt)) || !Array.isArray(value.validatedSources) || value.validatedSources.length !== 2 || value.validatedSources[0] !== "oracle" || value.validatedSources[1] !== "mariaDb" || typeof value.themeCanMutate !== "boolean" || typeof value.expertCanMutate !== "boolean" || typeof value.writeQuarantined !== "boolean" || (value.writeQuarantined && (value.themeCanMutate || value.expertCanMutate))) return null; if (expectedRequest && value.requestId !== expectedRequest.requestId) return null; return Object.freeze({ ...value, validatedSources: Object.freeze([...value.validatedSources]) }); } const mutationOperations = new Set(["create", "replace", "delete"]); function normalizeMutationResult(value, expected) { if (!hasExactKeys(value, [ "requestId", "entity", "operation", "identityCode", "operationId", "committedAt", "writeQuarantined" ]) || !requestIdPattern.test(value.requestId) || (value.entity !== "theme" && value.entity !== "expert") || !mutationOperations.has(value.operation) || !(value.entity === "theme" ? themeCodePattern : expertCodePattern).test(value.identityCode) || !uuidPattern.test(value.operationId) || !isSafeCanonicalText(value.committedAt, 64) || !Number.isFinite(Date.parse(value.committedAt)) || value.writeQuarantined !== false) return null; if (expected && ["requestId", "entity", "operation", "identityCode"] .some(key => value[key] !== expected[key])) return null; return Object.freeze({ ...value }); } const errorCodes = new Set([ "INVALID_REQUEST", "DATABASE_UNAVAILABLE", "INVALID_DATA", "INVALID_SCHEMA", "DATABASE_ERROR", "PLAYOUT_PRIORITY", "PLAYOUT_ACTIVE", "DATABASE_BUSY", "WRITE_BUSY", "CONFLICT", "WRITE_FAILED", "CANCELLED", "OUTCOME_UNKNOWN" ]); function normalizeCatalogError(value, expected) { if (!hasExactKeys(value, [ "requestId", "entity", "operation", "identityCode", "code", "message", "retryable", "outcomeUnknown", "writeQuarantined" ]) || (value.requestId !== "" && !requestIdPattern.test(value.requestId)) || !["theme", "expert", "catalog"].includes(value.entity) || !["list", "nextCode", "create", "replace", "delete", "schemaPreflight"].includes(value.operation) || typeof value.identityCode !== "string" || value.identityCode.length > 8 || !errorCodes.has(value.code) || !isSafeCanonicalText(value.message, 512) || typeof value.retryable !== "boolean" || typeof value.outcomeUnknown !== "boolean" || typeof value.writeQuarantined !== "boolean" || (value.outcomeUnknown && (value.retryable || !value.writeQuarantined)) || (mutationOperations.has(value.operation) && value.retryable)) return null; if (expected && ["requestId", "entity", "operation", "identityCode"] .some(key => value[key] !== expected[key])) return null; return Object.freeze({ ...value }); } function describeMutationRequest(type, payload) { try { switch (type) { case bridgeContract.requests.themeCreate: { const request = createThemeCreateRequest(payload?.requestId, payload?.draft); return Object.freeze({ requestId: request.requestId, entity: "theme", operation: "create", identityCode: request.draft.themeCode }); } case bridgeContract.requests.themeReplace: { if (!hasExactKeys(payload, ["requestId", "expectedIdentity", "newTitle", "items"])) return null; const request = createThemeReplaceRequest(payload.requestId, { expectedIdentity: payload.expectedIdentity, draft: { market: payload.expectedIdentity?.market, themeCode: payload.expectedIdentity?.themeCode, themeTitle: payload.newTitle, items: payload.items } }); return Object.freeze({ requestId: request.requestId, entity: "theme", operation: "replace", identityCode: request.expectedIdentity.themeCode }); } case bridgeContract.requests.themeDelete: { if (!hasExactKeys(payload, ["requestId", "identity"])) return null; const request = createThemeDeleteRequest(payload.requestId, payload.identity); return Object.freeze({ requestId: request.requestId, entity: "theme", operation: "delete", identityCode: request.identity.themeCode }); } case bridgeContract.requests.expertCreate: { const request = createExpertCreateRequest(payload?.requestId, payload?.draft); return Object.freeze({ requestId: request.requestId, entity: "expert", operation: "create", identityCode: request.draft.expertCode }); } case bridgeContract.requests.expertReplace: { if (!hasExactKeys(payload, ["requestId", "expectedIdentity", "newName", "recommendations"])) return null; const request = createExpertReplaceRequest(payload.requestId, { expectedIdentity: payload.expectedIdentity, draft: { expertCode: payload.expectedIdentity?.expertCode, expertName: payload.newName, recommendations: payload.recommendations } }); return Object.freeze({ requestId: request.requestId, entity: "expert", operation: "replace", identityCode: request.expectedIdentity.expertCode }); } case bridgeContract.requests.expertDelete: { if (!hasExactKeys(payload, ["requestId", "identity"])) return null; const request = createExpertDeleteRequest(payload.requestId, payload.identity); return Object.freeze({ requestId: request.requestId, entity: "expert", operation: "delete", identityCode: request.identity.expertCode }); } default: return null; } } catch { return null; } } function createMutationCoordinator() { let active = null; let quarantined = false; return Object.freeze({ begin(type, payload) { if (quarantined) throw new Error("Operator catalog writes are quarantined for this process."); if (active) throw new Error("Another operator catalog write is already active."); const description = describeMutationRequest(type, payload); if (!description) throw new TypeError("A valid operator catalog mutation request is required."); active = description; return description; }, acceptResult(payload) { if (!active) return null; const result = normalizeMutationResult(payload, active); if (!result) return null; active = null; return result; }, acceptError(payload) { if (!active) return null; const error = normalizeCatalogError(payload, active); if (!error) return null; if (error.outcomeUnknown || error.writeQuarantined) quarantined = true; active = null; return error; }, loseCorrelation() { if (active) quarantined = true; active = null; }, getState() { return Object.freeze({ active, quarantined }); } }); } return { bridgeContract, limits: Object.freeze({ maximumThemeItems: MAXIMUM_THEME_ITEMS, maximumRecommendations: MAXIMUM_RECOMMENDATIONS, maximumResults: MAXIMUM_RESULTS, maximumQueryLength: MAXIMUM_QUERY_LENGTH }), normalizeThemeIdentity, normalizeThemeDraft, normalizeExpertIdentity, normalizeExpertDraft, createThemeCatalogListRequest, createExpertCatalogListRequest, createSchemaPreflightRequest, createThemeNextCodeRequest, createExpertNextCodeRequest, normalizeThemeCatalogSelection, normalizeExpertCatalogSelection, createThemeEditDraft, createExpertEditDraft, createBlankThemeDraft, createBlankExpertDraft, createThemeCreateRequest, createThemeReplaceRequest, createThemeDeleteRequest, createExpertCreateRequest, createExpertReplaceRequest, createExpertDeleteRequest, normalizeThemeListResult, normalizeExpertListResult, normalizeThemeNextCodeResult, normalizeExpertNextCodeResult, normalizeSchemaResult, normalizeMutationResult, normalizeCatalogError, describeMutationRequest, createMutationCoordinator }; });