/* * ★ 필터가 없을 때도 시각순으로 낸다 — 상한이 시간축의 임의 구간을 잘라내던 자리. * * ── 무엇이 났나 (2026-08-25 실측 · 승화푸드) ───────────────────────────────── * 공정 타임라인은 필터 없이 상한만 걸고 지난 기록을 부른다. * * loadTwinEvents(id, { eventType: 'task.status', limit: TASK_CAP }) * * 그 갈래의 정렬이 `revision DESC` 였다. `revision` 은 **적힌 순서**다. 연결된 시스템을 되읽는 * 트윈에서는 적힌 순서와 일어난 순서가 다르다 — 커넥터가 연결된 시스템의 갱신 순서를 걷기 때문에, * 그쪽에서 나중에 손댄 행이 4월 사실을 담고 있으면 그 행이 큰 revision 을 받는다. * * 작업 축에 있는 가장 늦은 사실 2026-08-23 03:20 * revision 이 가장 큰 작업 사실 2026-04-15 10:49 ← 상한이 잡던 값 * * 상한 안에 8월 두 달치가 들어오지 않았고, 화면은 4월 사실을 최근 것으로 그렸다. 사용자가 세 번 * 짚었다. 화면이 낸 「앞부분이 빠져 있습니다」도 사실과 달랐다 — 빠진 것은 앞부분이 아니라 시간축의 * 임의 구간이었다. * * ── 성능은 이유가 되지 않는다 ─────────────────────────────────────────────── * 오히려 시각 정렬이 인덱스(`ix_twin_event_1`)를 순서대로 타서 빠르다. 같은 파일의 실측 기록: * `revision DESC` 200건 33.4초 · `eventTime DESC, revision DESC` 200건 0.007초. * * ── `revision` 순서를 없애지는 않는다 ─────────────────────────────────────────── * 부르는 쪽이 `revision` 범위로 물었으면 그 순서가 물음의 답이다. 거르는 축과 정렬 축을 맞추는 * 규칙과도 같다(`journal-sort-axis.test.ts`). */ import { test } from 'node:test' import assert from 'node:assert/strict' import { journalOrder } from '../server/service/twin-journal/journal-order.js' test('★ 아무 필터도 없으면 시각순이다 — 타임라인이 부르는 방식이 이것이다', () => { assert.deepEqual(journalOrder({}), { eventTime: 'DESC', revision: 'DESC' }) assert.deepEqual(journalOrder({ untilTime: null, fromRevision: null, toRevision: null }), { eventTime: 'DESC', revision: 'DESC' }) }) test('★ 시각으로 물으면 시각순이다 — 거르는 축과 정렬 축이 맞아야 상한이 일한다', () => { assert.deepEqual(journalOrder({ untilTime: '2026-08-25T00:00:00.000Z' }), { eventTime: 'DESC', revision: 'DESC' }) }) test('★ `revision` 범위로 물으면 그 순서로 낸다 — 그때는 그것이 물음의 답이다', () => { assert.deepEqual(journalOrder({ fromRevision: 100, toRevision: 200 }), { revision: 'DESC' }) assert.deepEqual(journalOrder({ toRevision: 200 }), { revision: 'DESC' }) assert.deepEqual(journalOrder({ fromRevision: 100 }), { revision: 'DESC' }) }) test('시각과 `revision` 이 함께 오면 시각이 이긴다 — 거르는 쪽이 그렇게 정해져 있다', () => { /* * `where` 절이 `untilTime` 을 먼저 보고 `revision` 조건을 쓰지 않는다. 정렬만 `revision` 으로 두면 축이 * 어긋나고, 그 어긋남이 바로 위 실측의 33.4초다. */ assert.deepEqual(journalOrder({ untilTime: '2026-08-25T00:00:00.000Z', fromRevision: 1, toRevision: 9 }), { eventTime: 'DESC', revision: 'DESC' }) }) test('동시각의 순서는 `revision` 이 정한다 — 둘째 축을 없애지 않는다', () => { /* 같은 시각에 여러 사실이 들어오는 것은 흔하다(한 번의 폴링이 여러 행을 낸다). 그때 순서가 없으면 화면이 새로 고칠 때마다 줄 순서가 바뀐다. */ for (const q of [{}, { untilTime: '2026-08-25T00:00:00.000Z' }]) { assert.equal(journalOrder(q).revision, 'DESC') } }) test('구간으로 물으면 시각순이다 — 화면이 그리는 구간을 그대로 묻는 길', () => { assert.deepEqual(journalOrder({ fromTime: '2026-08-22T00:00:00.000Z' }), { eventTime: 'DESC', revision: 'DESC' }) assert.deepEqual( journalOrder({ fromTime: '2026-08-22T00:00:00.000Z', untilTime: '2026-08-23T00:00:00.000Z' }), { eventTime: 'DESC', revision: 'DESC' } ) /* 구간과 `revision` 범위가 함께 와도 시각이 이긴다 — 거르는 쪽이 그렇게 정해져 있다. */ assert.deepEqual( journalOrder({ fromTime: '2026-08-22T00:00:00.000Z', fromRevision: 1, toRevision: 9 }), { eventTime: 'DESC', revision: 'DESC' } ) })