/* 416 헬퍼 — 한 페이지 끊김 ② : 진짜 상세 가게(`B06_Section_Section_Store`) · 진짜 평면 횡단 판 자료 * (`B05_Profile_UI_RouteEdit_Cross_Card.routeCrossSource`) · 진짜 관 길이(`design_structure_pipe_calc`) · * 진짜 페이지 노선 칸. 서버 · 그림 · 묶음은 가짜. 짝 = `test_416_page_cache.py`. * L2 — 판 = 페이지 노선 상세(더 큰 노선 번호 상세가 캐시에 있어도) * M5 — 관 길이 = 공용 캐시(페이지 노선) · 저장 뒤 비우고 다시 받으면 새 값 * M1 — 비운 뒤 두 보기가 함께 받으면 같은 새 객체 하나 * 암선 기본 = config(기억해 둔 context 값) */ const fs = require("fs"); const path = require("path"); const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); const CATALOG = require("./helper_322_catalog_module.cjs"); const ROOT = path.join(__dirname, "..", ".."); const P = "P416"; function memoryStorage() { const map = new Map(); return { getItem: (k) => (map.has(k) ? map.get(k) : null), setItem: (k, v) => map.set(k, String(v)), removeItem: (k) => map.delete(k), key: (i) => [...map.keys()][i] ?? null, get length() { return map.size; }, }; } globalThis.window = { sessionStorage: memoryStorage(), localStorage: memoryStorage() }; const anyModule = (extra = {}) => new Proxy(extra, { get: (t, key) => (key in t ? t[key] : () => undefined) }); /** 가짜 서버 — 노선마다 관 길이 하나(40 m 측점 관) · 받을 때마다 새 객체 · 받은 수. */ const server = { pipeLength: { 3: 8, 9: 30 }, fetches: 0 }; const detailOf = (routeId) => ({ longitudinal: { stations: [], design_profiles: [] }, cross_sections: [0, 20, 40].map((chainage_m) => ({ station_id: `r${routeId}-${chainage_m}`, chainage_m, samples: [], culvert: chainage_m === 40 ? { chainage_m: 40 } : undefined, design: { pipe_length_m: chainage_m === 40 ? server.pipeLength[routeId] : undefined }, })), }); let rockDefault = null; // 종단 보기가 기억한 context 값 let draftRoute = null; // 묶음 노선 초안 const STUBS = { "design_pipeline_page.ts": { reconcilePagePipeline: async () => 0, pipelineOwnsCross: () => false, pagePipeline: () => null, pipelineCross: () => undefined, pipelineDetail: () => null, }, "design_pipeline_cross.ts": { forceCross: () => undefined }, "B06_Section_Section_Drafts.ts": { forgetDraftStations: () => undefined, withDraftShapes: async () => undefined, withDraftStations: async () => undefined, }, "B05_Profile_Api_PlusStations.ts": { loadSavedPlusStations: async () => undefined, plusStationsOf: () => [], }, "B05_Profile_Api_Pipes_Draft.ts": { readPendingPipes: () => null }, "design_page_data.ts": { pagePipes: async () => ({ pipe_points: [] }) }, "B06_Section_Api_Fetch.ts": anyModule({ fetchSectionDetail: async (_projectId, routeId) => { server.fetches += 1; return detailOf(routeId); }, }), "B06_Section_Api_Culvert_Options.ts": anyModule({ STRUCTURE_ADJUST_KEYS: [] }), "B06_Section_Swap_Bus.ts": anyModule(), "B06_Section_Station_Keys.ts": anyModule({ KEYED_STATES: [] }), "B06_Section_UI_Page_Persist.ts": anyModule(), "B06_Section_UI_Standard_Panel.ts": anyModule({ readRockBoundaryDefault: () => rockDefault, }), "B06_Section_Cross_Design_Session.ts": anyModule(), "common_util_cross_design.ts": anyModule(), "B06_Section_Structure_Layouts.ts": anyModule({ computeStoredLayouts: () => null }), "B06_Section_UI_Cross_Culvert_Wire.ts": anyModule(), "B06_Section_UI_Cross_Ford_Geom.ts": anyModule(), "B06_Section_UI_Cross_Box_Geom.ts": anyModule(), "B06_Section_UI_Cross_View.ts": anyModule(), "B06_Section_UI_Section_Common.ts": anyModule({ designElevationAt: () => undefined }), "B06_Section_Display_Width.ts": anyModule({ displayHalfWidth: () => 10 }), "B06_Section_UI_Style_Cross_Controls.css": {}, "B06_Section_UI_Cross_Culvert_Basin.ts": anyModule(), "B06_Section_UI_Cross_Culvert_Const.ts": anyModule(), "B06_Section_UI_IrregularStations.ts": anyModule({ PIPE_DISPLAY_NAME: {} }), "B05_Profile_UI_IrregularStations.ts": anyModule({ PIPE_DISPLAY_NAME: {} }), "config_frontend.ts": anyModule({ PIPE_TYPES: [] }), }; const cache = new Map(); const resolve = (base) => [base, `${base}.ts`, path.join(base, "index.ts")].find( (p) => fs.existsSync(p) && fs.statSync(p).isFile(), ) ?? `${base}.ts`; function loadTs(full) { const stub = STUBS[path.basename(full)]; if (stub) return stub; if (cache.has(full)) return cache.get(full); const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020, esModuleInterop: true, }, }).outputText; const box = { exports: {} }; cache.set(full, box.exports); const near = (request) => { if (CATALOG.isCatalogRequest(request)) return CATALOG; const alias = { "@util/": "common_util", "@ui/": "ui_template", "@config/": "config" }; for (const [prefix, dir] of Object.entries(alias)) if (request.startsWith(prefix)) return loadTs(resolve(path.join(ROOT, dir, request.slice(prefix.length)))); return loadTs(resolve(path.join(path.dirname(full), request))); }; new Function("exports", "module", "require", js)(box.exports, box, near); return box.exports; } const store = loadTs(path.join(ROOT, "B06_Section", "B06_Section_Section_Store.ts")); const pageRoute = loadTs(path.join(ROOT, "A00_Common", "design", "design_page_route.ts")); const pipeCalc = loadTs(path.join(ROOT, "A00_Common", "design", "design_structure_pipe_calc.ts")); const card = loadTs(path.join(ROOT, "B05_Profile", "B05_Profile_UI_RouteEdit_Cross_Card.ts")); const fakePipe = { input: (name) => (name === "route" ? draftRoute : null) }; async function run() { const out = {}; // 열 때 — 페이지 노선 3 · 캐시에 더 큰 노선 9 도 있음(다른 탭 · 옛 판) pageRoute.setPageRouteId(P, 3); await store.loadSectionDetail(P, 9); const shown = await store.loadSectionDetail(P, 3); const source = card.routeCrossSource(P, fakePipe, 40, "cur"); out.l2 = { station: source?.section.station_id ?? null, noRoute: store.peekSectionDetail(P, null), exact: store.peekSectionDetail(P, 3) === shown, }; draftRoute = { v: 1 }; out.l2.draftNoSaved = card.routeCrossSource(P, fakePipe, 40, "cur"); // 노선 초안 중 = 저장 상세 안 씀 draftRoute = null; // 암선 기본 = config(기억값) · 기억 전 = 서버 Schema 기본과 같은 값 out.rock = { pending: source.rockDefaultM }; rockDefault = -0.8; out.rock.config = card.routeCrossSource(P, fakePipe, 40, "cur").rockDefaultM; // M5 — 관 길이 = 공용 캐시(페이지 노선) 한 벌(design_page 가 거는 그 함수) pipeCalc.setPipeSectionsSource( () => store.peekSectionDetail(P, pageRoute.pageRouteId(P))?.cross_sections ?? null, ); out.m5 = { before: pipeCalc.pipeLengthM(40) }; // [저장] — 서버가 관 길이를 다시 셈(8 → 11) · 한 벌이 캐시를 한 번 비움 → 두 보기가 함께 다시 받음 server.pipeLength[3] = 11; store.invalidateSectionDetail(P); out.m5.cleared = pipeCalc.pipeLengthM(40); const fetchesBefore = server.fetches; const [profileView, crossView] = await Promise.all([ store.loadSectionDetail(P, 3), store.loadSectionDetail(P, 3), ]); out.m1 = { sameObject: profileView === crossView, fetched: server.fetches - fetchesBefore, fresh: profileView !== shown, panelSeesFresh: card.routeCrossSource(P, fakePipe, 40, "cur")?.section.station_id ?? null, }; out.m5.after = pipeCalc.pipeLengthM(40); return out; } run() .then((out) => console.log(JSON.stringify(out))) .catch((error) => { console.error(error); process.exit(1); });