fetchEvent — TTL キャッシュと last-good
fetchEvent(config, { slug?, ttlMs? }) は GET /slug/{eventSlug} を叩き、type === "event" のときだけ FourSEvent を返す。stub / mock-locked では null、失敗時も null(render を落とさない)。
キャッシュは SDK 内蔵の in-memory TTL(既定 60 秒)である。本番 payload は ~3.5MB で Next の fetch cache 上限(2MB)を超えるため Next data cache は実質効かない。warm な serverless インスタンス間では best-effort で共有される。さらに last-good 動作を持つ: 障害時の null は既存キャッシュが無いときだけ保存し、直前の正常値を消さない。
const event = res && res.type === "event" ? (res.data as FourSEvent) : null;
// 障害時に直前の正常値を消さない: null は既存キャッシュが無いときだけ保存
if (event || !hit) eventCache.set(slug, { at: Date.now(), event });
return event ?? hit?.event ?? null;
sessionSpeakerRefs — 新旧 API の吸収
4S の session↔speaker 参照には新旧2系統がある。sessionSpeakerRefs(raw) が唯一の入口:
- 新 API:
sessionSpeakers[]をorder昇順に整列し.speakerを取り出す - 旧 API fallback:
sessionSpeakersが空なら legacy のspeakers[]をそのまま返す
detectModeratorId — 4S が唯一の真実源
detectModeratorId(raw, roleMatcher?) は sessionSpeakers[].role を DEFAULT_MODERATOR_ROLE_RE = /moderat|モデレ|司会/i で照合し、order 順で最初に一致した speaker id を返す。CMS の手動上書き・「先頭を自動的にモデレーター扱い」はどちらも廃止済みの設計であり、Session.moderatorId は毎回導出し、保存しない。イベント固有の表記は moderatorRoleMatcher オプションで上書きする。
adaptSession — overlay seam と last-writer-wins
ivs の 3-layer overlay(stage-meta / session-meta / KV)はイベント固有なので SDK に持ち込まない。代わりに overlay: (sessionId) => Partial<Session> コールバックを generic な継ぎ目として受け取り、@event/cms 側が content JSON / KV を注入する。
手動ピックアップ cardSpeakerIds と 4S 側 lineup 編集の競合は last-writer-wins で解決する。4S 側の基準時刻は fourSLineupEditedAt(raw)(session.updatedAt と各 sessionSpeakers[].updatedAt の最大値。ISO-8601 UTC は辞書順比較が時刻順になる):
const fourSAt = fourSLineupEditedAt(raw);
const csi = overlay.cardSpeakerIds;
const csiAt = overlay.cardSpeakerIdsUpdatedAt;
const cardSpeakerWins = !!(csi && csi.length > 0 && (!csiAt || !fourSAt || csiAt >= fourSAt));
- overlay 保存時刻 >= 4S 更新時刻 → 手動を採用
- タイムスタンプ無し(legacy な明示的人手)→ 採用
- 4S の方が新しい → 手動を捨てて 4S 全員・4S 順
- 4S に居ない id は無視。結果が空なら 4S 全員へフォールバック
LOCKED フィールド(id / title / titleEn / startsAt / durationMin / roomId / speakerIds)は overlay の後に spread されるため上書き不可。cardVariant は overlay の枠数が実登壇者数を下回ると登壇者が黙って欠けるため、枠数 >= 人数 のときだけ手動値を尊重する。
adaptSpeaker は nativeName 優先の氏名合成(pickNameJa / pickNameEn)、languages.en からの英語フィールド抽出、stripCorpName による法人格除去(既定 on、overlay 値には非適用)を行う。listSpeakersFromEvent は top-level speakers[] と session 埋め込みを結合し、同一 user の重複 speaker レコード(4S の既知挙動)を user 単位で畳む。
merge と safety guard
CMS content と 4S の合成は pure 関数で行う(xdeal 由来):
mergeCollection(layer1, layer3, lockedKeys, meta)— union merge。4S に無いローカルレコードも残す(seed フェーズ用)mergeCollectionCanonical(...)— 4S を一覧の正とし、content は CMS-only フィールドの温存にだけ使う。stale なローカルレコードを混入させないoverlayLocked(base, locked, lockedKeys)— nonEmpty の locked 値のみ上書き
sync / 書き戻し時のデータ全損は safetyGuard({ existing, fresh, force }) が防ぐ:
| 条件 | 判定 |
|---|---|
| existing < 5 | 書く(初期 seed フェーズ) |
| fresh = 0 | skip(4S 障害時の全消去防止) |
| fresh < existing × 30% | skip(大幅減は要警戒。force で上書き可) |
| それ以外 / force | 書く |
戻り値 GuardDecision は理由文字列を含み、cron ログにそのまま出せる。