From d0cf8f7ab44cc31058b1ffac41b64852d6d61a0d Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 7 Nov 2026 16:06:47 +0000 Subject: [PATCH] fix(cli): os diff notes that an insert seed dataset is inserted again on replay seedModeNote explained the replace arm and the upsert/update arm but said nothing for insert, whose loader arm (SeedLoaderService.writeRecord and decideWriteAction) inserts every record with no match, exactly like replace. The plan now says so, and the diff test pins the note on an insert dataset added and changed, with an ignore dataset as the silent control. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_017ihL2bzGzPEiCJX3i1fYRj --- .changeset/cli-diff-insert-seed-note.md | 8 +++++++ packages/cli/src/commands/diff.ts | 1 + packages/cli/test/diff-upgrade-plan.test.ts | 24 +++++++++++++++++++++ 3 files changed, 33 insertions(+) create mode 100644 .changeset/cli-diff-insert-seed-note.md diff --git a/.changeset/cli-diff-insert-seed-note.md b/.changeset/cli-diff-insert-seed-note.md new file mode 100644 index 00000000000..bd96436585f --- /dev/null +++ b/.changeset/cli-diff-insert-seed-note.md @@ -0,0 +1,8 @@ +--- +'@objectstack/cli': patch +--- + +fix(cli): `os diff` now says what a replay does to a seed dataset in mode `insert`, as it already did for `replace`: a replay inserts its records again, and a row the database refuses is that row's error. The loader's `insert` arm inserts every record without looking for a match, so the plan no longer reads an `insert` dataset as harmless. + +Clause-②: no +Metadata-protocol: none diff --git a/packages/cli/src/commands/diff.ts b/packages/cli/src/commands/diff.ts index 5a8ab9b986c..b02482c057c 100644 --- a/packages/cli/src/commands/diff.ts +++ b/packages/cli/src/commands/diff.ts @@ -79,6 +79,7 @@ function diffNamedArrays( function seedModeNote(mode: string): string { if (seedModeOverwritesMatchedRows(mode)) return '; a replay writes its columns over the rows it matches'; if (mode === 'replace') return '; a replay deletes nothing and inserts these again, so rows already in the table stay'; + if (mode === 'insert') return "; a replay inserts these again, and a row the database refuses is that row's error"; return ''; } diff --git a/packages/cli/test/diff-upgrade-plan.test.ts b/packages/cli/test/diff-upgrade-plan.test.ts index 8bf07d95b24..6ef764a7dbb 100644 --- a/packages/cli/test/diff-upgrade-plan.test.ts +++ b/packages/cli/test/diff-upgrade-plan.test.ts @@ -153,4 +153,28 @@ describe('os diff — what the upgrade plan adds', () => { { type: 'modified', category: 'data', name: 'acme_region', breaking: false, detail: 'seed dataset changed: records; mode ignore' }, ]); }); + + it('an insert dataset, added or changed, says a replay inserts its records again; ignore stays silent', () => { + const before = lead({}, [ + { object: 'acme_source', mode: 'insert', records: [{ name: 'web' }] }, + { object: 'acme_region', mode: 'ignore', records: [{ name: 'emea' }] }, + ]); + const after = lead({}, [ + { object: 'acme_source', mode: 'insert', records: [{ name: 'web' }, { name: 'event' }] }, + { object: 'acme_region', mode: 'ignore', records: [{ name: 'apac' }] }, + { object: 'acme_channel', mode: 'insert', records: [{ name: 'email' }] }, + ]); + const insertNote = "a replay inserts these again, and a row the database refuses is that row's error"; + expect(diffStacks(before, after)).toEqual([ + { + type: 'added', category: 'data', name: 'acme_channel', breaking: false, + detail: `seed dataset added: 1 record(s); mode insert; ${insertNote}`, + }, + { + type: 'modified', category: 'data', name: 'acme_source', breaking: false, + detail: `seed dataset changed: records; mode insert; ${insertNote}`, + }, + { type: 'modified', category: 'data', name: 'acme_region', breaking: false, detail: 'seed dataset changed: records; mode ignore' }, + ]); + }); });