← 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 →