Package Exports
- @emat.tw/connector
- @emat.tw/connector/hub
- @emat.tw/connector/protocol
- @emat.tw/connector/react
- @emat.tw/connector/types
Readme
@emat.tw/connector
將 Next.js 站點連接至 emat 中央後台。提供 heartbeat、feature flags、遠端控制、事件回報 四個能力,整個 SDK 目標 < 5 kB gzipped。
目前版本
0.5.0— 加上/hubsubpath(heartbeat ingest handler + 參考 in-memory store)。尚未對活體 Hub 做 E2E。Production 請小心。
安裝
pnpm add @emat.tw/connector需求:Node ≥ 18、Next ≥ 15、React ≥ 18。
Quick start
// lib/site.ts
import { createConnector } from '@emat.tw/connector'
import { revalidateTag } from 'next/cache'
export const site = createConnector({
flags: {
new_editor: { default: false, label: '新版編輯器' },
maintenance: { default: false, label: '維護模式' },
},
controls: {
'clear-cache': {
label: '清除快取',
run: () => revalidateTag('all'),
},
},
})業務程式裡:
import { site } from '@/lib/site'
// 讀 flag(本地 cache,預設 TTL 30 秒,Hub 不通時回 default)
// inline 宣告或 satisfies 時,TS 會強制 key 必須在宣告裡,
// 並把回傳型別 narrow 成該 flag default 的型別。
if (await site.flag('new_editor')) { // Promise<boolean>
// ...
}
// 回報事件(fire-and-forget,永不 throw)
site.report('news.published', { id: article.id })
// 顯式 heartbeat(不必常用,回傳 BeatResult)
const res = await site.heartbeat()
if (!res.ok) console.warn('hub unreachable:', res.error)
// 內部狀態快照(debug / 健檢)
console.log(site.snapshot())
// { siteId, version, lastBeatAt, flags, pendingEvents, pendingReceipts, lastSchemaHash }環境變數
SITE_ID=my-site-slug # 每個站點獨一無二
SITE_SECRET=… # Hub 簽發的 token
HUB_URL=https://hub.emat.tw # Hub base URL三個缺一時 Connector 會 console.warn 並進入 no-op 模式:flag() 一律回 default、report() 直接忽略、heartbeat() 不送出。設計原則是「設定失誤絕對不讓站點壞掉」。
Config 參考
| 欄位 | 預設 | 說明 |
|---|---|---|
siteId |
env.SITE_ID |
站點識別 |
secret |
env.SITE_SECRET |
共享密鑰 |
hubUrl |
env.HUB_URL |
Hub base URL |
flags |
{} |
Record<string, FlagDefinition> |
controls |
{} |
Record<string, ControlDefinition> |
heartbeatCooldown |
60 |
每次 heartbeat 最小間隔(秒) |
flagCacheTTL |
30 |
Flag 本地 cache TTL(秒) |
version |
env / "unknown" |
回報給 Hub 的站點版本 |
selfHosted |
false |
站點本身就是 Hub 時設 true,跳過 env warn 與 network beat |
logger |
console.* |
注入 { warn, error? } 自訂 logger(傳空函式可完全靜音) |
onBeat |
— | (result) => void 每次 heartbeat 後觸發,用來餵 metrics |
完整型別:src/types.ts。
Hub subpath(接 heartbeat 的那一端)
Hub 那邊裝同一個 package,從 /hub import:
// app/api/connector/heartbeat/route.ts (Next.js)
import { createHeartbeatHandler } from "@emat.tw/connector/hub"
import { hubStore } from "@/lib/hub-store"
export const POST = createHeartbeatHandler({ store: hubStore })hubStore 實作 HubStore 介面 — 7 個方法接你選的儲存(D1 / KV / Postgres / Firebase…)。Demo 跟測試可以直接用內建的:
import { createMemoryStore } from "@emat.tw/connector/hub"
export const hubStore = createMemoryStore()
hubStore.registerSite("emat", process.env.SITE_SECRET!)handler 回傳 Web 標準 Response,所以 Next route handler / Hono / raw Node 都裝得上。狀態碼 200 / 400 / 401 / 426 / 500。
React subpath(client 元件用)
當 satellite 有 client 元件需要讀 flag,不要每個元件 fetch——server 端讀完一次塞進 context:
// app/layout.tsx — server component
import { ConnectorProvider } from "@emat.tw/connector/react"
import { site } from "@/lib/site"
export default function Layout({ children }) {
return (
<ConnectorProvider flags={site.snapshot().flags}>
{children}
</ConnectorProvider>
)
}// 任何 client 元件
"use client"
import { useFlag } from "@emat.tw/connector/react"
export function NewEditorButton() {
const enabled = useFlag("new_editor", false)
if (!enabled) return null
return <button>新版編輯器</button>
}Server 讀 → context → client 同步取,零 hydration mismatch、零額外 HTTP request。
執行環境
支援:
- Server Components
- Server Actions
- Route Handlers(
runtime: 'nodejs')
不支援:
- Edge Runtime(
runtime: 'edge') - Middleware
若在 Edge 路由需要讀 flag,請改由 Server Component / Server Action 包一層後呼叫。
Wire Protocol 概要
Connector 與 Hub 之間只有一個 endpoint:
POST {HUB_URL}/api/connector/heartbeatRequest headers:
| Header | 值 |
|---|---|
x-site-id |
站點 ID |
x-site-secret |
共享密鑰 |
x-protocol-version |
1 |
送出(HeartbeatRequest):site 元資料、schema hash(hash 變動時才帶 full schema)、本批 events、前輪 commands 的 receipts。
收回(HeartbeatResponse):flags 現值、待執行 commands、schema ack。
完整型別:src/protocol.ts。
執行流程
cold start
└─▶ heartbeat ─▶ Hub 回 flags + commands
├─ 收到 commands → 跑 control.run() → 記錄 receipt
└─ cache flags (TTL 30s)
業務碼呼叫 flag() / report()
├─ 距上次 beat > 60s ─▶ 背景再 beat 一次(不 block 呼叫者)
└─ 距上次 beat ≤ 60s ─▶ 讀 cache 即回
Hub 不通
├─ flag() ─▶ 回宣告的 default
├─ report() ─▶ 吞下(event 進 buffer 等下次 beat)
└─ 站點完全不受影響開發
pnpm build # 編譯到 dist/
pnpm dev # watch mode
pnpm typecheck # 純型別檢查Roadmap
0.0.1:公開 API + 型別 + stub 實作 ✓0.1.0:HTTP 傳輸、schema diff、flag cache、command loop ✓0.2.0:BeatResult、snapshot()、自訂 logger、subpath exports ✓0.3.0:typed flag keys、onBeat hook ✓0.4.0:@emat.tw/connector/react(<ConnectorProvider>+useFlag) ✓0.4.1:vitest 單元測試(26 tests) ✓0.5.0← 目前:@emat.tw/connector/hub(heartbeat handler + memory store)0.6.0:E2E(對真實 Hub)、metrics / OpenTelemetry hooks0.7.0:CLI scaffold(npx @emat.tw/connector init)
授權
MIT © emat.tw