[object Object]

← back to Filemaker Mcp

fm-client: dedupe guard on createRecord — block duplicate WALLPAPER masters (Series+JS Pattern)

0ac2c193bf5232b401b4c7dc8ba6fc00df35ebfd · 2026-08-19 11:25:44 -0700 · steve

Files touched

Diff

commit 0ac2c193bf5232b401b4c7dc8ba6fc00df35ebfd
Author: steve <steve@designerwallcoverings.com>
Date:   Wed Aug 19 11:25:44 2026 -0700

    fm-client: dedupe guard on createRecord — block duplicate WALLPAPER masters (Series+JS Pattern)
---
 src/fm-client.js | 28 ++++++++++++++++++++++++++--
 1 file changed, 26 insertions(+), 2 deletions(-)

diff --git a/src/fm-client.js b/src/fm-client.js
index deb7798..2ab55c6 100644
--- a/src/fm-client.js
+++ b/src/fm-client.js
@@ -158,12 +158,36 @@ export async function deleteRecord(db, layout, recordId) {
   return { deleted: true, database: db, recordId };
 }
 
-export async function createRecord(db, layout, fieldData, { dryRun = true } = {}) {
+// Identity keys that define a duplicate master, per database. Same values across
+// these fields = the same combo SKU (e.g. WALLPAPER Series 'GRS' + JS Pattern '43023'
+// = GRS-43023). Guards against the duplicate-master bug (2026-08-19): re-running an
+// onboarding script must never mint a 2nd/3rd copy of an existing master.
+const DEDUPE_KEYS = { WALLPAPER: ['Series', 'JS Pattern'] };
+
+// Does a record with this fieldData's identity already exist?
+async function findDuplicate(db, layout, fieldData) {
+  const keys = DEDUPE_KEYS[db];
+  if (!keys || !keys.every(k => String(fieldData[k] ?? '').trim() !== '')) return null;
+  const query = {}; for (const k of keys) query[k] = '==' + String(fieldData[k]).trim();
+  const r = await findRecords(db, layout, query, { limit: 1 }).catch(() => ({ records: [] }));
+  return r.records?.[0] || null;
+}
+
+// createRecord now guards against duplicate masters by default. Pass allowDuplicate:true
+// to intentionally bypass (rare). The guard runs in dry-run too (reports wouldSkip).
+export async function createRecord(db, layout, fieldData, { dryRun = true, allowDuplicate = false } = {}) {
   assertWritable(db);
   const lay = resolveLayout(db, layout);
+  let dup = null;
+  if (!allowDuplicate) dup = await findDuplicate(db, layout, fieldData);
   if (dryRun) {
     return { committed: false, dryRun: true, database: db, layout: lay, fieldData,
-      note: 'Call again with dryRun:false to create this record.' };
+      wouldSkipDuplicate: !!dup, existingRecordId: dup?.recordId || null,
+      note: dup ? `DUPLICATE: a master with the same ${(DEDUPE_KEYS[db]||[]).join('+')} already exists (record ${dup.recordId}); would skip.` : 'Call again with dryRun:false to create this record.' };
+  }
+  if (dup) {
+    return { committed: false, skipped: 'duplicate', database: db, layout: lay, existingRecordId: dup.recordId,
+      note: `Skipped create — duplicate master (${(DEDUPE_KEYS[db]||[]).join('+')}) already exists as record ${dup.recordId}. Pass allowDuplicate:true to override.` };
   }
   const r = await fm(db, `/layouts/${encodeURIComponent(lay)}/records`, { method: 'POST', body: { fieldData } });
   return { committed: true, dryRun: false, database: db, layout: lay, recordId: r.recordId, modId: r.modId };

← 0b28984 GRS mfr recovery: FMP->Shopify fill (425 written, reversible  ·  back to Filemaker Mcp  ·  GRS dedup review viewer + client-safe scoping + FM flag/arch 2dd6a25 →