diff --git a/registry/examples/km-wiki-ingest/drainer/README.md b/registry/examples/km-wiki-ingest/drainer/README.md index d7d6a33..f5273cc 100644 --- a/registry/examples/km-wiki-ingest/drainer/README.md +++ b/registry/examples/km-wiki-ingest/drainer/README.md @@ -57,10 +57,13 @@ printf '%s' "$GITEA_TOKEN" | npx wrangler secret put GITEA_TOKEN - Secret:若設,同步 `GITEA_WEBHOOK_SECRET` secret(本 Worker 驗 `X-Gitea-Signature` HMAC-SHA256) - Gitea 的 ping event → Worker 回 `{ok:true,pong:true}`(測連通) -## config(wrangler.toml [vars]) +## config -`REPO=Leo/notes REF=main OWNER=leo CARDS_ROOT=system-dev/wiki/cards BATCH_SIZE=2` -`CODE_WORKER_URL / KBDB_URL / GRAPH_URL / GRAPH_KEY`(皆 leo21c live 服務)。 +- **vars**:`REPO=Leo/notes REF=main OWNER=leo CARDS_ROOT=system-dev/wiki/cards GRAPH_KEY=leo BATCH_SIZE=2` +- **Service Bindings(`[[services]]`)**:`SVC_CODE→arcrun-code`、`SVC_KBDB→arcrun-kbdb`、`SVC_GRAPH→kbdb-graph-plugin`。 + ⚠️ 同帳號 worker-to-worker **必須走 Service Binding**(`env.SVC_X.fetch()`)——直接 `fetch("https://arcrun-*.leo21c.workers.dev")` + 會撞 **CF 1042**(同帳號 worker 互打限制)。Gitea(`git.uncle6.me`,非 CF)才用 plain `fetch`。 + 註:`wrangler dev` 本地 miniflare **不強制** 1042,故本地測會假綠 → 一律對部署後真端點驗收。 ## 規模備註(5,083 檔) diff --git a/registry/examples/km-wiki-ingest/drainer/src/index.ts b/registry/examples/km-wiki-ingest/drainer/src/index.ts index 1c0ba65..ef9d548 100644 --- a/registry/examples/km-wiki-ingest/drainer/src/index.ts +++ b/registry/examples/km-wiki-ingest/drainer/src/index.ts @@ -28,15 +28,21 @@ export interface Env { REF: string; // main OWNER: string; // leo(KBDB 租戶 owner_id) CARDS_ROOT: string; // system-dev/wiki/cards - CODE_WORKER_URL: string; // https://arcrun-code.leo21c.workers.dev/ - KBDB_URL: string; // https://arcrun-kbdb.leo21c.workers.dev - GRAPH_URL: string; // https://kbdb-graph-plugin.leo21c.workers.dev + // 同帳號 worker-to-worker 必須走 Service Binding(直接 fetch(*.leo21c.workers.dev) 會撞 CF 1042)。 + SVC_CODE: Fetcher; // → arcrun-code + SVC_KBDB: Fetcher; // → arcrun-kbdb + SVC_GRAPH: Fetcher; // → kbdb-graph-plugin GRAPH_KEY: string; // leo BATCH_SIZE: string; // "2" GITEA_TOKEN: string; // secret:讀 repo GITEA_WEBHOOK_SECRET?: string; // secret(選填):驗 webhook 簽章 } +// Service Binding fetch 輔助:host 是 placeholder(binding 直接路由到目標 worker),path/query/method/body 才重要。 +function svc(binding: Fetcher, path: string, init?: RequestInit): Promise { + return binding.fetch(new Request(`https://svc${path}`, init)); +} + const CURSOR_TYPE = 'ingest_cursor'; const cursorPageName = (env: Env) => `cursor:km_wiki_ingest_drain:${env.REPO}`; @@ -73,7 +79,7 @@ async function giteaRaw(env: Env, relPath: string): Promise { // ── card-processing 核心 ────────────────────────────────────────────────────── async function parseCard(env: Env, md: string, relPath: string): Promise { - const r = await fetch(env.CODE_WORKER_URL, { + const r = await svc(env.SVC_CODE, '/', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ @@ -99,20 +105,20 @@ async function upsertEntry(env: Env, entry: any): Promise<{ action: string; id?: const pageName = entry.page_name; const metadata_json = JSON.stringify(entry.metadata); const newHash = entry.metadata.content_hash; - const lookup = await fetch( - `${env.KBDB_URL}/entries?page_name=${encodeURIComponent(pageName)}&owner_id=${encodeURIComponent(env.OWNER)}`, + const lookup = await svc(env.SVC_KBDB, + `/entries?page_name=${encodeURIComponent(pageName)}&owner_id=${encodeURIComponent(env.OWNER)}`, ); const existing = ((await lookup.json()) as { entries?: any[] }).entries?.[0]; if (existing) { if (metaHash(existing) === newHash) return { action: 'skip', id: existing.id }; - const r = await fetch(`${env.KBDB_URL}/entries/${existing.id}`, { + const r = await svc(env.SVC_KBDB, `/entries/${existing.id}`, { method: 'PATCH', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ content: entry.content, tags_json: JSON.stringify(entry.tags || []), metadata_json }), }); return { action: 'patch', id: existing.id, ...(r.ok ? {} : { action: 'patch-failed' }) }; } - const r = await fetch(`${env.KBDB_URL}/entries`, { + const r = await svc(env.SVC_KBDB, `/entries`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ @@ -134,7 +140,7 @@ async function postEnvelopes(env: Env, envelopes: any[]): Promise { for (const envp of envelopes) { // envelopes 已由 code 節點剝除 _-鍵;此處再保險剝一次。 const clean = Object.fromEntries(Object.entries(envp).filter(([k]) => !k.startsWith('_'))); - const r = await fetch(`${env.GRAPH_URL}/triplets/ingest?owner_id=${encodeURIComponent(env.OWNER)}`, { + const r = await svc(env.SVC_GRAPH, `/triplets/ingest?owner_id=${encodeURIComponent(env.OWNER)}`, { method: 'POST', headers: { 'content-type': 'application/json', 'X-Arcrun-API-Key': env.GRAPH_KEY }, body: JSON.stringify(clean), @@ -168,8 +174,8 @@ async function processCard(env: Env, relPath: string): Promise { interface CursorState { last_path: string; cycle: number; processed_total: number; updated_at: number; } async function getCursor(env: Env): Promise<{ id?: string; state: CursorState }> { - const r = await fetch( - `${env.KBDB_URL}/entries?page_name=${encodeURIComponent(cursorPageName(env))}&owner_id=${encodeURIComponent(env.OWNER)}`, + const r = await svc(env.SVC_KBDB, + `/entries?page_name=${encodeURIComponent(cursorPageName(env))}&owner_id=${encodeURIComponent(env.OWNER)}`, ); const e = ((await r.json()) as { entries?: any[] }).entries?.[0]; if (e) { @@ -182,11 +188,11 @@ async function getCursor(env: Env): Promise<{ id?: string; state: CursorState }> async function setCursor(env: Env, cur: { id?: string; state: CursorState }): Promise { const content = JSON.stringify(cur.state); if (cur.id) { - await fetch(`${env.KBDB_URL}/entries/${cur.id}`, { + await svc(env.SVC_KBDB, `/entries/${cur.id}`, { method: 'PATCH', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ content }), }); } else { - await fetch(`${env.KBDB_URL}/entries`, { + await svc(env.SVC_KBDB, `/entries`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ entry_type: CURSOR_TYPE, page_name: cursorPageName(env), owner_id: env.OWNER, content, metadata_json: JSON.stringify({ kind: 'ingest_cursor', embed: false }) }), diff --git a/registry/examples/km-wiki-ingest/drainer/wrangler.toml b/registry/examples/km-wiki-ingest/drainer/wrangler.toml index 658b1e3..631eb2d 100644 --- a/registry/examples/km-wiki-ingest/drainer/wrangler.toml +++ b/registry/examples/km-wiki-ingest/drainer/wrangler.toml @@ -3,20 +3,27 @@ main = "src/index.ts" compatibility_date = "2025-02-19" workers_dev = true -# ⚠️ 維持「非-auto」:預設不註冊 cron trigger(不自走排程、不無人值守亂寫)。 -# drain 以 POST /drain 手動觸發(與 scheduled handler 同核心)。 -# 要開自走 Phase 0 backfill:leo 過閘後,取消下面兩行註解再 deploy(scheduled handler 已在 code)。 +# ⚠️ 維持「非-auto」:預設不註冊 cron trigger(不自走排程)。drain 走 POST /drain(與 scheduled 同核心)。 +# 要開自走 Phase 0 backfill:leo 過閘後取消下面兩行註解再 deploy(scheduled handler 已在 code)。 # [triggers] # crons = ["*/5 * * * *"] +# ── Service Bindings(同帳號 worker-to-worker 正解;直接 fetch(*.leo21c.workers.dev) 會撞 CF 1042)── +[[services]] +binding = "SVC_CODE" +service = "arcrun-code" +[[services]] +binding = "SVC_KBDB" +service = "arcrun-kbdb" +[[services]] +binding = "SVC_GRAPH" +service = "kbdb-graph-plugin" + [vars] REPO = "Leo/notes" REF = "main" OWNER = "leo" CARDS_ROOT = "system-dev/wiki/cards" -CODE_WORKER_URL = "https://arcrun-code.leo21c.workers.dev/" -KBDB_URL = "https://arcrun-kbdb.leo21c.workers.dev" -GRAPH_URL = "https://kbdb-graph-plugin.leo21c.workers.dev" GRAPH_KEY = "leo" BATCH_SIZE = "2" # secret(wrangler secret put):GITEA_TOKEN(必),GITEA_WEBHOOK_SECRET(選)