From 8192f13ae02dee2b3535a089d2578b877cceaaec Mon Sep 17 00:00:00 2001 From: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Date: Sat, 6 Jun 2026 22:01:14 +0800 Subject: [PATCH] =?UTF-8?q?fix(runtime):=20seed-apply=20hook=20=E2=80=94?= =?UTF-8?q?=20pass=20`seeds`=20(not=20`datasets`)=20+=20resilient=20read-b?= =?UTF-8?q?ack?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The publish-drafts seed-apply hook (applyPublishedSeeds) never actually loaded rows on a remote-Turso staging env. Two defects: 1. Wrong field: it called SeedLoaderRequestSchema.parse({ datasets, ... }) but the field was renamed `datasets`→`seeds` in the same PR — this newly-added constructor was missed (parse takes `unknown`, so tsc didn't catch it). Pass `seeds: datasets`. 2. Scope-fragile read-back: it read each just-published seed with the active org only and swallowed misses into an empty list, then reported success/0-rows. A workspace seed is typically stored env-wide (organization_id IS NULL), so the org-scoped read found nothing. Now try the active-org read then fall back to an env-wide read, and if no seed body is readable return success:false with a diagnostic (never claim success while loading 0 rows). Manually verified end-to-end on staging: drafts now publish + the object goes live; this lands the final step — published seed rows actually load. Co-Authored-By: Claude Opus 4.8 --- packages/runtime/src/http-dispatcher.ts | 52 +++++++++++++++++-------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/packages/runtime/src/http-dispatcher.ts b/packages/runtime/src/http-dispatcher.ts index 1b3441828..d05e89a36 100644 --- a/packages/runtime/src/http-dispatcher.ts +++ b/packages/runtime/src/http-dispatcher.ts @@ -1757,30 +1757,48 @@ export class HttpDispatcher { return { success: false, error: 'seed apply: required services unavailable' }; } const datasets: any[] = []; + const readErrors: string[] = []; for (const name of names) { - try { - const item: any = await protocol.getMetaItem({ - type: 'seed', - name, - ...(organizationId ? { organizationId } : {}), - }); - // getMetaItem returns the item body directly; tolerate a - // wrapper ({metadata|body}) just in case. - const seed = item?.object && Array.isArray(item?.records) - ? item - : (item?.metadata ?? item?.body); - if (seed?.object && Array.isArray(seed?.records)) datasets.push(seed); - } catch { - /* skip an unreadable seed; keep applying the rest */ + // Read the just-published seed body. Try the active org first, then + // fall back to an env-wide read — a workspace seed is often stored + // org-wide (organization_id IS NULL), and resolving the wrong scope + // here is what silently produced "0 rows loaded". + const attempts = organizationId + ? [{ type: 'seed', name, organizationId }, { type: 'seed', name }] + : [{ type: 'seed', name }]; + let item: any; + for (const args of attempts) { + try { + item = await protocol.getMetaItem(args); + if (item) break; + } catch (e) { + readErrors.push(`read ${name}: ${(e as Error)?.message ?? String(e)}`); + } + } + // getMetaItem returns the item body directly; tolerate a wrapper. + const seed = item?.object && Array.isArray(item?.records) + ? item + : (item?.metadata ?? item?.body); + if (seed?.object && Array.isArray(seed?.records)) { + datasets.push(seed); + } else { + readErrors.push(`seed "${name}" body unreadable (keys: ${item ? Object.keys(item).join(',') : 'none'})`); } } - if (datasets.length === 0) return { success: true, inserted: 0, updated: 0 }; + // Seeds were published but none could be read back → surface it (do NOT + // report success with 0 rows, which hides the failure). + if (datasets.length === 0) { + return { success: false, inserted: 0, updated: 0, error: 'seed apply: no readable seed bodies', errors: readErrors }; + } const { SeedLoaderService } = await import('./seed-loader.js'); const { SeedLoaderRequestSchema } = await import('@objectstack/spec/data'); const loader = new SeedLoaderService(ql, metadata, (this as any).logger ?? console); const request = SeedLoaderRequestSchema.parse({ - datasets, + // ADR field is `seeds` (renamed from `datasets`); this constructor + // was added in the same PR and the rename missed it — passing + // `datasets` left `seeds` undefined and the loader saw nothing. + seeds: datasets, config: { defaultMode: 'upsert', multiPass: true, @@ -1792,7 +1810,7 @@ export class HttpDispatcher { success: r.success, inserted: r.summary.totalInserted, updated: r.summary.totalUpdated, - errors: r.errors, + errors: [...readErrors, ...(r.errors ?? [])], }; }