diff --git a/.changeset/seed-replayer-skipped.md b/.changeset/seed-replayer-skipped.md new file mode 100644 index 0000000000..2e583096d7 --- /dev/null +++ b/.changeset/seed-replayer-skipped.md @@ -0,0 +1,19 @@ +--- +"@objectstack/runtime": patch +--- + +feat(runtime): seed-replayer reports `skipped` so hosts can stamp seed-once on progress + +The `seed-replayer` kernel service returned `{ inserted, updated, errors }` but +not `skipped`. A cloud host therefore could not tell an **all-skip replay** +(the env's seed data is already present — a no-op) apart from the +zero-summary early-returns that never ran the loader (no organization, no +metadata service, no datasets). Both looked like `inserted = updated = 0`, so +the host could not safely stamp its seed-once record for the all-skip case and +re-ran the full remote replay on every cold boot. + +Add `skipped: result.summary.totalSkipped` to the replayer's return; the +early-returns report `skipped: 0`. This lets a host (cloud#853's +`decideSeedStamp`) stamp on progress — including an all-skip replay — while +still declining to stamp a genuine no-loader zero-summary. Additive and +backward compatible; existing consumers ignore the new field. diff --git a/packages/runtime/src/app-plugin.ts b/packages/runtime/src/app-plugin.ts index 220a6429fe..cb192f1122 100644 --- a/packages/runtime/src/app-plugin.ts +++ b/packages/runtime/src/app-plugin.ts @@ -765,17 +765,17 @@ export class AppPlugin implements Plugin { const metadataNow = ctx.getService('metadata') as IMetadataService | undefined; const loggerRef = ctx.logger; const replayer = async (organizationId: string) => { - if (!organizationId) return { inserted: 0, updated: 0, errors: [] as any[] }; + if (!organizationId) return { inserted: 0, updated: 0, skipped: 0, errors: [] as any[] }; const md = metadataNow ?? (ctx.getService('metadata') as IMetadataService | undefined); if (!md) { loggerRef.warn('[seed-replayer] metadata service unavailable'); - return { inserted: 0, updated: 0, errors: [] as any[] }; + return { inserted: 0, updated: 0, skipped: 0, errors: [] as any[] }; } const datasetsNow = (() => { try { return kernel?.getService?.('seed-datasets'); } catch { return merged; } })() ?? merged; if (!Array.isArray(datasetsNow) || datasetsNow.length === 0) { - return { inserted: 0, updated: 0, errors: [] as any[] }; + return { inserted: 0, updated: 0, skipped: 0, errors: [] as any[] }; } const seedLoader = new SeedLoaderService(ql, md, loggerRef); const { SeedLoaderRequestSchema } = await import('@objectstack/spec/data'); @@ -796,6 +796,12 @@ export class AppPlugin implements Plugin { return { inserted: result.summary.totalInserted, updated: result.summary.totalUpdated, + // `skipped` lets a cloud host distinguish an all-skip + // replay (data already present) from the zero-summary + // early-returns above (which never ran the loader), so + // it can stamp its seed-once record on progress rather + // than re-replaying every cold boot (cloud#853). + skipped: result.summary.totalSkipped, errors: result.errors, }; };