@nextbridgehq/payload-block-builder 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,1228 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+
19
+ // src/index.ts
20
+ var src_exports = {};
21
+ __export(src_exports, {
22
+ BlockDefinitionVersions: () => BlockDefinitionVersions,
23
+ BlockDefinitions: () => BlockDefinitions,
24
+ dbLayoutField: () => dbLayoutField,
25
+ dynamicBlocksPlugin: () => dynamicBlocksPlugin
26
+ });
27
+ module.exports = __toCommonJS(src_exports);
28
+
29
+ // src/plugin.ts
30
+ var import_crypto = require("crypto");
31
+
32
+ // src/access/authenticated.ts
33
+ var authenticated = ({ req: { user } }) => Boolean(user);
34
+
35
+ // src/collections/BlockDefinitions.ts
36
+ var BlockDefinitions = {
37
+ slug: "block-definitions",
38
+ admin: {
39
+ useAsTitle: "name",
40
+ defaultColumns: ["name", "slug", "currentVersion", "updatedAt"],
41
+ group: "Dynamic Blocks",
42
+ hidden: true
43
+ },
44
+ access: {
45
+ create: authenticated,
46
+ read: authenticated,
47
+ update: authenticated,
48
+ delete: authenticated
49
+ },
50
+ fields: [
51
+ {
52
+ name: "name",
53
+ type: "text",
54
+ required: true,
55
+ admin: { description: 'Human-readable label, e.g. "Hero Section"' }
56
+ },
57
+ {
58
+ name: "slug",
59
+ type: "text",
60
+ required: true,
61
+ unique: true,
62
+ admin: { description: 'Machine identifier, e.g. "hero-section". Should not change after creation.' }
63
+ },
64
+ {
65
+ name: "description",
66
+ type: "textarea",
67
+ required: false
68
+ },
69
+ {
70
+ name: "currentVersion",
71
+ type: "relationship",
72
+ relationTo: "block-definition-versions",
73
+ required: false,
74
+ admin: {
75
+ description: "The active schema version used when editors add a new instance of this block."
76
+ }
77
+ },
78
+ {
79
+ name: "editInBuilderButton",
80
+ type: "ui",
81
+ admin: {
82
+ components: {
83
+ Field: "@nextbridgehq/payload-block-builder/client#EditInBuilderButton"
84
+ }
85
+ }
86
+ }
87
+ ]
88
+ };
89
+
90
+ // src/collections/BlockDefinitionVersions.ts
91
+ var BlockDefinitionVersions = {
92
+ slug: "block-definition-versions",
93
+ admin: {
94
+ useAsTitle: "label",
95
+ defaultColumns: ["blockDefinition", "versionNumber", "label", "createdAt"],
96
+ group: "Dynamic Blocks",
97
+ hidden: true
98
+ },
99
+ access: {
100
+ create: authenticated,
101
+ read: authenticated,
102
+ update: () => false,
103
+ delete: authenticated
104
+ },
105
+ fields: [
106
+ {
107
+ name: "blockDefinition",
108
+ type: "relationship",
109
+ relationTo: "block-definitions",
110
+ required: true,
111
+ admin: { description: "Which block type this version belongs to." }
112
+ },
113
+ {
114
+ name: "versionNumber",
115
+ type: "number",
116
+ required: true,
117
+ admin: { description: "Monotonically increasing integer, e.g. 1, 2, 3." }
118
+ },
119
+ {
120
+ name: "label",
121
+ type: "text",
122
+ required: false,
123
+ admin: { description: 'Optional display name, e.g. "v2 \xE2\u20AC\u201D added hero image".' }
124
+ },
125
+ {
126
+ name: "schema",
127
+ type: "json",
128
+ required: true,
129
+ admin: {
130
+ description: 'JSON object with a "fields" array of BlockFieldDefinition objects. Use the visual editor below to build the schema.',
131
+ components: {
132
+ Field: "@nextbridgehq/payload-block-builder/client#SchemaBuilderField"
133
+ }
134
+ }
135
+ },
136
+ {
137
+ name: "changelog",
138
+ type: "textarea",
139
+ required: false,
140
+ admin: { description: "Notes on what changed in this version." }
141
+ }
142
+ ]
143
+ };
144
+
145
+ // src/fields/dbLayoutField.ts
146
+ function dbLayoutField(fieldName = "dbLayout", tabLabel = "DB Layout") {
147
+ return {
148
+ label: tabLabel,
149
+ fields: [
150
+ {
151
+ name: fieldName,
152
+ type: "array",
153
+ label: "DB Layout Blocks",
154
+ admin: {
155
+ description: "Dynamic blocks rendered on this page. Add blocks, select a block definition and version, then fill in the data fields that appear.",
156
+ initCollapsed: true
157
+ },
158
+ fields: [
159
+ {
160
+ name: "blockDefinition",
161
+ type: "relationship",
162
+ relationTo: "block-definitions",
163
+ required: true,
164
+ admin: { description: "Which block type to use." }
165
+ },
166
+ {
167
+ name: "blockVersion",
168
+ type: "relationship",
169
+ relationTo: "block-definition-versions",
170
+ required: true,
171
+ admin: { description: "Which schema version to use." }
172
+ },
173
+ {
174
+ name: "instanceId",
175
+ type: "text",
176
+ admin: {
177
+ hidden: true,
178
+ readOnly: true,
179
+ description: "Auto-assigned unique ID for this block instance."
180
+ }
181
+ },
182
+ {
183
+ name: "label",
184
+ type: "text",
185
+ admin: {
186
+ description: "Optional label to identify this block in the list."
187
+ }
188
+ },
189
+ {
190
+ name: "data",
191
+ type: "json",
192
+ admin: {
193
+ description: "Block field data. Automatically populated from the selected schema.",
194
+ components: {
195
+ Field: "@nextbridgehq/payload-block-builder/client#BlockDataField"
196
+ }
197
+ }
198
+ },
199
+ {
200
+ name: "hidden",
201
+ type: "checkbox",
202
+ defaultValue: false,
203
+ admin: { description: "Hide this block on the frontend." }
204
+ },
205
+ {
206
+ name: "anchor",
207
+ type: "text",
208
+ admin: { description: "Optional HTML anchor ID for deep-linking." }
209
+ }
210
+ ]
211
+ }
212
+ ]
213
+ };
214
+ }
215
+
216
+ // src/block-builder/lib/codegen.ts
217
+ function indent(n) {
218
+ return " ".repeat(n);
219
+ }
220
+ function escStr(s) {
221
+ return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
222
+ }
223
+ function fieldToCode(field, depth = 1) {
224
+ const pad = indent(depth);
225
+ const innerPad = indent(depth + 1);
226
+ const lines = [];
227
+ lines.push(`${pad}name: '${escStr(field.name)}'`);
228
+ lines.push(`${pad}type: '${field.type}'`);
229
+ if (field.label) lines.push(`${pad}label: '${escStr(field.label)}'`);
230
+ if (field.required) lines.push(`${pad}required: true`);
231
+ if (field.unique) lines.push(`${pad}unique: true`);
232
+ if (field.localized) lines.push(`${pad}localized: true`);
233
+ if (field.defaultValue !== void 0) {
234
+ const val = typeof field.defaultValue === "string" ? `'${escStr(String(field.defaultValue))}'` : field.defaultValue;
235
+ lines.push(`${pad}defaultValue: ${val}`);
236
+ }
237
+ if (field.type === "richText") {
238
+ lines.push(`${pad}editor: lexicalEditor({})`);
239
+ }
240
+ if (field.options && field.options.length > 0) {
241
+ const opts = field.options.map((o) => `{ label: '${escStr(o.label)}', value: '${escStr(o.value)}' }`).join(`, `);
242
+ lines.push(`${pad}options: [${opts}]`);
243
+ }
244
+ if (field.relationTo) {
245
+ lines.push(`${pad}relationTo: '${escStr(field.relationTo)}'`);
246
+ }
247
+ if (field.hasMany !== void 0) {
248
+ lines.push(`${pad}hasMany: ${field.hasMany}`);
249
+ }
250
+ if (field.minRows !== void 0) lines.push(`${pad}minRows: ${field.minRows}`);
251
+ if (field.maxRows !== void 0) lines.push(`${pad}maxRows: ${field.maxRows}`);
252
+ if (field.fields && field.fields.length > 0) {
253
+ const nested = field.fields.map((f) => `${innerPad}{
254
+ ${fieldToCode(f, depth + 2)}
255
+ ${innerPad}}`).join(",\n");
256
+ lines.push(`${pad}fields: [
257
+ ${nested}
258
+ ${innerPad}]`);
259
+ }
260
+ const adminParts = [];
261
+ if (field.admin?.description)
262
+ adminParts.push(`description: '${escStr(field.admin.description)}'`);
263
+ if (field.admin?.placeholder)
264
+ adminParts.push(`placeholder: '${escStr(field.admin.placeholder)}'`);
265
+ if (field.admin?.readOnly) adminParts.push(`readOnly: true`);
266
+ if (field.admin?.hidden) adminParts.push(`hidden: true`);
267
+ if (adminParts.length > 0) {
268
+ lines.push(`${pad}admin: { ${adminParts.join(", ")} }`);
269
+ }
270
+ return lines.join(",\n");
271
+ }
272
+ function generateBlockCode(block) {
273
+ const hasRichText = containsRichText(block.fields);
274
+ const imports = [`import type { Block } from 'payload'`];
275
+ if (hasRichText) {
276
+ imports.push(`import { lexicalEditor } from '@payloadcms/richtext-lexical'`);
277
+ }
278
+ const fieldsCode = block.fields.map((f) => ` {
279
+ ${fieldToCode(f, 2)}
280
+ }`).join(",\n");
281
+ const labelsCode = block.labels ? `
282
+ labels: {
283
+ singular: '${escStr(block.labels.singular ?? block.slug)}',
284
+ plural: '${escStr(block.labels.plural ?? block.slug + "s")}',
285
+ },` : "";
286
+ const interfaceLine = block.interfaceName ? `
287
+ interfaceName: '${escStr(block.interfaceName)}',` : "";
288
+ const exportName = block.interfaceName ?? toCamelCase(block.slug);
289
+ return [
290
+ imports.join("\n"),
291
+ "",
292
+ `export const ${exportName}: Block = {`,
293
+ ` slug: '${escStr(block.slug)}',${interfaceLine}${labelsCode}`,
294
+ ` fields: [`,
295
+ fieldsCode,
296
+ ` ],`,
297
+ `}`,
298
+ ""
299
+ ].join("\n");
300
+ }
301
+ function containsRichText(fields) {
302
+ return fields.some(
303
+ (f) => f.type === "richText" || (f.fields ? containsRichText(f.fields) : false)
304
+ );
305
+ }
306
+ function toCamelCase(slug) {
307
+ return slug.split(/[-_]/).map(
308
+ (part, i) => i === 0 ? part : part.charAt(0).toUpperCase() + part.slice(1)
309
+ ).join("");
310
+ }
311
+ function generateBlockOutput(block) {
312
+ return {
313
+ filename: `${block.slug}.ts`,
314
+ code: generateBlockCode(block),
315
+ language: "typescript"
316
+ };
317
+ }
318
+ function generateAllBlocks(blocks) {
319
+ return blocks.map(generateBlockOutput);
320
+ }
321
+ function generateIndexFile(blocks) {
322
+ const exportName = (b) => b.interfaceName ?? toCamelCase(b.slug);
323
+ const imports = blocks.map((b) => `import { ${exportName(b)} } from './${b.slug}'`).join("\n");
324
+ const exportList = blocks.map((b) => ` ${exportName(b)}`).join(",\n");
325
+ const code = [
326
+ imports,
327
+ "",
328
+ `export const blocks = [`,
329
+ exportList,
330
+ `] as const`,
331
+ ""
332
+ ].join("\n");
333
+ return { filename: "index.ts", code, language: "typescript" };
334
+ }
335
+
336
+ // src/endpoints/generate.ts
337
+ var generateEndpoint = async (req) => {
338
+ if (!req.user) {
339
+ return Response.json({ error: "Unauthorized" }, { status: 401 });
340
+ }
341
+ let blocks;
342
+ try {
343
+ if (!req.json) return Response.json({ error: "No JSON parser available" }, { status: 500 });
344
+ const body = await req.json();
345
+ blocks = body.blocks ?? [];
346
+ } catch {
347
+ return Response.json({ error: "Invalid JSON body" }, { status: 400 });
348
+ }
349
+ const blockOutputs = generateAllBlocks(blocks);
350
+ const indexOutput = generateIndexFile(blocks);
351
+ const fileMap = {};
352
+ for (const out of blockOutputs) {
353
+ fileMap[out.filename] = out.code;
354
+ }
355
+ fileMap[indexOutput.filename] = indexOutput.code;
356
+ return Response.json({ files: fileMap });
357
+ };
358
+
359
+ // src/block-builder/lib/schemaToBuilderBlock.ts
360
+ var import_uuid = require("uuid");
361
+ var REVERSE_TYPE_MAP = {
362
+ richtext: "richText",
363
+ image: "upload"
364
+ };
365
+ var VALID_BUILDER_TYPES = /* @__PURE__ */ new Set([
366
+ "text",
367
+ "textarea",
368
+ "number",
369
+ "email",
370
+ "checkbox",
371
+ "select",
372
+ "radio",
373
+ "date",
374
+ "richText",
375
+ "upload",
376
+ "relationship",
377
+ "array",
378
+ "group",
379
+ "tabs",
380
+ "row",
381
+ "collapsible",
382
+ "json",
383
+ "code",
384
+ "point",
385
+ "ui"
386
+ ]);
387
+ function fieldToBuilderField(raw) {
388
+ const rawType = String(raw.type ?? "text");
389
+ const mappedType = REVERSE_TYPE_MAP[rawType] ?? rawType;
390
+ const fieldType = VALID_BUILDER_TYPES.has(mappedType) ? mappedType : "text";
391
+ const field = {
392
+ id: (0, import_uuid.v4)(),
393
+ type: fieldType,
394
+ name: String(raw.name ?? "field"),
395
+ label: raw.label ? String(raw.label) : void 0,
396
+ required: Boolean(raw.required)
397
+ };
398
+ if (raw.options && Array.isArray(raw.options)) {
399
+ field.options = raw.options.map(
400
+ (o) => typeof o === "string" ? { label: o, value: o } : { label: String(o.label), value: String(o.value) }
401
+ );
402
+ }
403
+ if (raw.hasMany !== void 0) field.hasMany = Boolean(raw.hasMany);
404
+ if (raw.collection) field.relationTo = String(raw.collection);
405
+ if (raw.relationTo) {
406
+ field.relationTo = String(raw.relationTo);
407
+ }
408
+ if (raw.minRows !== void 0) field.minRows = Number(raw.minRows);
409
+ if (raw.maxRows !== void 0) field.maxRows = Number(raw.maxRows);
410
+ if (raw.fields && Array.isArray(raw.fields)) {
411
+ field.fields = raw.fields.map(fieldToBuilderField);
412
+ }
413
+ if (raw.admin && typeof raw.admin === "object") {
414
+ const a = raw.admin;
415
+ field.admin = {
416
+ description: a.description ? String(a.description) : void 0,
417
+ placeholder: a.placeholder ? String(a.placeholder) : void 0,
418
+ readOnly: Boolean(a.readOnly),
419
+ hidden: Boolean(a.hidden)
420
+ };
421
+ }
422
+ return field;
423
+ }
424
+ function slugToInterfaceName(slug) {
425
+ return slug.split(/[-_]/).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
426
+ }
427
+ function schemaToBuilderBlock(slug, name, labels, schemaFields) {
428
+ return {
429
+ id: (0, import_uuid.v4)(),
430
+ slug,
431
+ interfaceName: slugToInterfaceName(slug),
432
+ labels,
433
+ fields: schemaFields.map(fieldToBuilderField)
434
+ };
435
+ }
436
+
437
+ // src/endpoints/load.ts
438
+ var loadEndpoint = async (req) => {
439
+ if (!req.user) {
440
+ return Response.json({ error: "Unauthorized" }, { status: 401 });
441
+ }
442
+ const slug = req.routeParams?.slug;
443
+ if (!slug) {
444
+ return Response.json({ error: "Slug is required" }, { status: 400 });
445
+ }
446
+ const requestedVersionId = req.url ? new URL(req.url).searchParams.get("versionId") : null;
447
+ const result = await req.payload.find({
448
+ collection: "block-definitions",
449
+ where: { slug: { equals: slug } },
450
+ depth: 2,
451
+ limit: 1
452
+ });
453
+ const def = result.docs[0];
454
+ if (!def) {
455
+ return Response.json({ error: `Block definition "${slug}" not found` }, { status: 404 });
456
+ }
457
+ const name = def.name ?? slug;
458
+ const currentVersionId = def.currentVersion && typeof def.currentVersion === "object" ? String(def.currentVersion.id) : typeof def.currentVersion === "string" || typeof def.currentVersion === "number" ? String(def.currentVersion) : null;
459
+ let version = null;
460
+ if (requestedVersionId) {
461
+ try {
462
+ const v = await req.payload.findByID({
463
+ collection: "block-definition-versions",
464
+ id: requestedVersionId,
465
+ depth: 0
466
+ });
467
+ version = v;
468
+ } catch {
469
+ return Response.json({ error: `Version "${requestedVersionId}" not found` }, { status: 404 });
470
+ }
471
+ } else if (def.currentVersion && typeof def.currentVersion === "object") {
472
+ version = def.currentVersion;
473
+ } else {
474
+ const latestResult = await req.payload.find({
475
+ collection: "block-definition-versions",
476
+ where: { blockDefinition: { equals: def.id } },
477
+ sort: "-versionNumber",
478
+ depth: 0,
479
+ limit: 1
480
+ });
481
+ version = latestResult.docs[0] ?? null;
482
+ }
483
+ if (!version) {
484
+ const block2 = schemaToBuilderBlock(slug, name, {}, []);
485
+ return Response.json({ block: block2, versionId: null, versionNumber: null, isCurrent: true });
486
+ }
487
+ const versionId = String(version.id);
488
+ const schema = version.schema;
489
+ const schemaFields = schema ? Array.isArray(schema) ? schema : schema.fields ?? [] : [];
490
+ const labels = version.labels ?? {};
491
+ const versionNumber = version.versionNumber;
492
+ const block = schemaToBuilderBlock(slug, name, labels, schemaFields);
493
+ return Response.json({
494
+ block,
495
+ versionId,
496
+ versionNumber: versionNumber ?? null,
497
+ isCurrent: versionId === currentVersionId
498
+ });
499
+ };
500
+
501
+ // src/builder/normalizer.ts
502
+ var KNOWN_TYPES = /* @__PURE__ */ new Set([
503
+ "text",
504
+ "textarea",
505
+ "richtext",
506
+ "number",
507
+ "checkbox",
508
+ "select",
509
+ "multiselect",
510
+ "date",
511
+ "image",
512
+ "file",
513
+ "url",
514
+ "email",
515
+ "color",
516
+ "array",
517
+ "group",
518
+ "relationship",
519
+ "json",
520
+ "blocks"
521
+ ]);
522
+ function normaliseOption(opt) {
523
+ if (typeof opt === "string") {
524
+ return { label: opt, value: opt.toLowerCase().replace(/\s+/g, "-") };
525
+ }
526
+ if (opt && typeof opt === "object") {
527
+ const o = opt;
528
+ const value = String(o.value ?? o.label ?? "").toLowerCase().replace(/\s+/g, "-");
529
+ const label = String(o.label ?? o.value ?? value);
530
+ return { label, value };
531
+ }
532
+ return { label: String(opt), value: String(opt) };
533
+ }
534
+ function normaliseConditions(raw) {
535
+ if (!Array.isArray(raw)) return void 0;
536
+ const result = [];
537
+ for (const item of raw) {
538
+ if (item && typeof item === "object" && !Array.isArray(item)) {
539
+ const c = item;
540
+ if (typeof c.field === "string" && typeof c.operator === "string") {
541
+ result.push({ field: c.field, operator: c.operator, value: c.value });
542
+ }
543
+ }
544
+ }
545
+ return result.length > 0 ? result : void 0;
546
+ }
547
+ function normaliseValidation(raw) {
548
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
549
+ const v = raw;
550
+ const out = {};
551
+ if (typeof v.required === "boolean") out.required = v.required;
552
+ if (typeof v.minLength === "number") out.minLength = v.minLength;
553
+ if (typeof v.maxLength === "number") out.maxLength = v.maxLength;
554
+ if (typeof v.regex === "string") out.regex = v.regex;
555
+ if (typeof v.min === "number") out.min = v.min;
556
+ if (typeof v.max === "number") out.max = v.max;
557
+ if (typeof v.step === "number") out.step = v.step;
558
+ if (typeof v.integerOnly === "boolean") out.integerOnly = v.integerOnly;
559
+ if (typeof v.minRows === "number") out.minRows = v.minRows;
560
+ if (typeof v.maxRows === "number") out.maxRows = v.maxRows;
561
+ if (typeof v.uniqueItems === "boolean") out.uniqueItems = v.uniqueItems;
562
+ if (Array.isArray(v.allowedMimeTypes)) out.allowedMimeTypes = v.allowedMimeTypes;
563
+ if (typeof v.maxFileSize === "number") out.maxFileSize = v.maxFileSize;
564
+ if (typeof v.maxSelections === "number") out.maxSelections = v.maxSelections;
565
+ return Object.keys(out).length > 0 ? out : void 0;
566
+ }
567
+ function normaliseUI(raw) {
568
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
569
+ const u = raw;
570
+ const out = {};
571
+ if (typeof u.tab === "string") out.tab = u.tab;
572
+ if (typeof u.section === "string") out.section = u.section;
573
+ if (["full", "half", "third", "quarter"].includes(u.width)) {
574
+ out.width = u.width;
575
+ }
576
+ if (typeof u.collapsed === "boolean") out.collapsed = u.collapsed;
577
+ if (typeof u.order === "number") out.order = u.order;
578
+ return Object.keys(out).length > 0 ? out : void 0;
579
+ }
580
+ function normaliseField(raw) {
581
+ const type = raw.type ?? "text";
582
+ const resolvedType = KNOWN_TYPES.has(type) ? type : "text";
583
+ const base = {
584
+ name: String(raw.name ?? "").trim(),
585
+ type: resolvedType
586
+ };
587
+ if (raw.label) base.label = String(raw.label);
588
+ if (raw.required !== void 0) base.required = Boolean(raw.required);
589
+ if (raw.admin && typeof raw.admin === "object") base.admin = raw.admin;
590
+ const conditions = normaliseConditions(raw.conditions);
591
+ if (conditions) base.conditions = conditions;
592
+ if (raw.conditionMode === "AND" || raw.conditionMode === "OR") {
593
+ base.conditionMode = raw.conditionMode;
594
+ }
595
+ const validation = normaliseValidation(raw.validation);
596
+ if (validation) base.validation = validation;
597
+ const ui = normaliseUI(raw.ui);
598
+ if (ui) base.ui = ui;
599
+ switch (resolvedType) {
600
+ case "text":
601
+ case "textarea": {
602
+ if (raw.minLength !== void 0) base.minLength = Number(raw.minLength);
603
+ if (raw.maxLength !== void 0) base.maxLength = Number(raw.maxLength);
604
+ if (raw.defaultValue !== void 0) base.defaultValue = raw.defaultValue;
605
+ break;
606
+ }
607
+ case "number": {
608
+ if (raw.min !== void 0) base.min = Number(raw.min);
609
+ if (raw.max !== void 0) base.max = Number(raw.max);
610
+ if (raw.defaultValue !== void 0) base.defaultValue = raw.defaultValue;
611
+ break;
612
+ }
613
+ case "checkbox": {
614
+ if (raw.defaultValue !== void 0) base.defaultValue = Boolean(raw.defaultValue);
615
+ break;
616
+ }
617
+ case "select":
618
+ case "multiselect": {
619
+ const rawOpts = Array.isArray(raw.options) ? raw.options : [];
620
+ base.options = rawOpts.map(normaliseOption);
621
+ if (raw.defaultValue !== void 0) base.defaultValue = raw.defaultValue;
622
+ break;
623
+ }
624
+ case "date": {
625
+ if (raw.timeFormat !== void 0) base.timeFormat = Boolean(raw.timeFormat);
626
+ break;
627
+ }
628
+ case "array": {
629
+ const subFields = Array.isArray(raw.fields) ? raw.fields.map(normaliseField) : [];
630
+ base.fields = subFields;
631
+ if (raw.minRows !== void 0) base.minRows = Number(raw.minRows);
632
+ if (raw.maxRows !== void 0) base.maxRows = Number(raw.maxRows);
633
+ break;
634
+ }
635
+ case "group": {
636
+ const subFields = Array.isArray(raw.fields) ? raw.fields.map(normaliseField) : [];
637
+ base.fields = subFields;
638
+ break;
639
+ }
640
+ case "relationship": {
641
+ if (raw.collection) base.collection = String(raw.collection);
642
+ if (raw.hasMany !== void 0) base.hasMany = Boolean(raw.hasMany);
643
+ break;
644
+ }
645
+ case "file": {
646
+ if (Array.isArray(raw.allowedMimeTypes)) base.allowedMimeTypes = raw.allowedMimeTypes;
647
+ break;
648
+ }
649
+ case "blocks": {
650
+ if (Array.isArray(raw.allowedBlocks)) {
651
+ base.allowedBlocks = raw.allowedBlocks.map(String).filter(Boolean);
652
+ }
653
+ if (raw.minBlocks !== void 0) base.minBlocks = Number(raw.minBlocks);
654
+ if (raw.maxBlocks !== void 0) base.maxBlocks = Number(raw.maxBlocks);
655
+ break;
656
+ }
657
+ }
658
+ return base;
659
+ }
660
+ function normaliseSchema(raw) {
661
+ const fields = Array.isArray(raw.fields) ? raw.fields.map(normaliseField) : [];
662
+ const schema = { fields };
663
+ if (raw.layout === "sidebar" || raw.layout === "tabs") {
664
+ schema.layout = raw.layout;
665
+ } else {
666
+ schema.layout = "default";
667
+ }
668
+ return schema;
669
+ }
670
+
671
+ // src/validation/schemaValidator.ts
672
+ var VALID_FIELD_TYPES = /* @__PURE__ */ new Set([
673
+ "text",
674
+ "textarea",
675
+ "richtext",
676
+ "number",
677
+ "checkbox",
678
+ "select",
679
+ "multiselect",
680
+ "date",
681
+ "image",
682
+ "file",
683
+ "url",
684
+ "email",
685
+ "color",
686
+ "array",
687
+ "group",
688
+ "relationship",
689
+ "json",
690
+ "blocks"
691
+ ]);
692
+ var CONTAINER_TYPES = /* @__PURE__ */ new Set(["array", "group"]);
693
+ var LEAF_NAME_RE = /^[a-zA-Z][a-zA-Z0-9_]*$/;
694
+ var VALID_CONDITION_OPERATORS = /* @__PURE__ */ new Set([
695
+ "equals",
696
+ "not_equals",
697
+ "contains",
698
+ "not_contains",
699
+ "greater_than",
700
+ "less_than",
701
+ "in",
702
+ "not_in",
703
+ "exists",
704
+ "empty"
705
+ ]);
706
+ function validateConditions(conditions, path, errors) {
707
+ if (!Array.isArray(conditions)) {
708
+ errors.push(`${path}: "conditions" must be an array.`);
709
+ return;
710
+ }
711
+ ;
712
+ conditions.forEach((cond, i) => {
713
+ const cp = `${path}[${i}]`;
714
+ if (!cond || typeof cond !== "object") {
715
+ errors.push(`${cp}: condition must be an object.`);
716
+ return;
717
+ }
718
+ const c = cond;
719
+ if (typeof c.field !== "string" || !c.field.trim()) {
720
+ errors.push(`${cp}: "field" must be a non-empty string.`);
721
+ }
722
+ if (typeof c.operator !== "string" || !VALID_CONDITION_OPERATORS.has(c.operator)) {
723
+ errors.push(
724
+ `${cp}: "operator" must be one of: ${[...VALID_CONDITION_OPERATORS].join(", ")}.`
725
+ );
726
+ }
727
+ });
728
+ }
729
+ function validateValidationRules(v, path, errors) {
730
+ const numericProps = [
731
+ "minLength",
732
+ "maxLength",
733
+ "min",
734
+ "max",
735
+ "step",
736
+ "minRows",
737
+ "maxRows",
738
+ "maxFileSize",
739
+ "maxSelections"
740
+ ];
741
+ for (const prop of numericProps) {
742
+ if (v[prop] !== void 0 && typeof v[prop] !== "number") {
743
+ errors.push(`${path}.${prop}: must be a number.`);
744
+ }
745
+ }
746
+ if (typeof v.minLength === "number" && typeof v.maxLength === "number" && v.minLength > v.maxLength) {
747
+ errors.push(`${path}: "minLength" (${v.minLength}) must be \xE2\u2030\xA4 "maxLength" (${v.maxLength}).`);
748
+ }
749
+ if (typeof v.min === "number" && typeof v.max === "number" && v.min > v.max) {
750
+ errors.push(`${path}: "min" (${v.min}) must be \xE2\u2030\xA4 "max" (${v.max}).`);
751
+ }
752
+ if (typeof v.minRows === "number" && typeof v.maxRows === "number" && v.minRows > v.maxRows) {
753
+ errors.push(`${path}: "minRows" (${v.minRows}) must be \xE2\u2030\xA4 "maxRows" (${v.maxRows}).`);
754
+ }
755
+ if (v.regex !== void 0) {
756
+ if (typeof v.regex !== "string") {
757
+ errors.push(`${path}.regex: must be a string.`);
758
+ } else {
759
+ try {
760
+ new RegExp(v.regex);
761
+ } catch {
762
+ errors.push(`${path}.regex: invalid regular expression "${v.regex}".`);
763
+ }
764
+ }
765
+ }
766
+ if (v.integerOnly !== void 0 && typeof v.integerOnly !== "boolean") {
767
+ errors.push(`${path}.integerOnly: must be a boolean.`);
768
+ }
769
+ if (v.uniqueItems !== void 0 && typeof v.uniqueItems !== "boolean") {
770
+ errors.push(`${path}.uniqueItems: must be a boolean.`);
771
+ }
772
+ if (v.allowedMimeTypes !== void 0 && !Array.isArray(v.allowedMimeTypes)) {
773
+ errors.push(`${path}.allowedMimeTypes: must be an array of strings.`);
774
+ }
775
+ }
776
+ function validateField(field, path, errors, warnings) {
777
+ if (!field || typeof field !== "object" || Array.isArray(field)) {
778
+ errors.push(`${path}: must be a non-array object.`);
779
+ return;
780
+ }
781
+ const f = field;
782
+ if (typeof f.name !== "string" || !f.name.trim()) {
783
+ errors.push(`${path}: "name" is required and must be a non-empty string.`);
784
+ } else if (!LEAF_NAME_RE.test(f.name)) {
785
+ errors.push(
786
+ `${path}.name "${f.name}": must start with a letter and contain only letters, digits, or underscores.`
787
+ );
788
+ }
789
+ if (typeof f.type !== "string") {
790
+ errors.push(`${path}: "type" is required.`);
791
+ return;
792
+ }
793
+ if (!VALID_FIELD_TYPES.has(f.type)) {
794
+ errors.push(`${path}: unknown field type "${f.type}".`);
795
+ return;
796
+ }
797
+ const type = f.type;
798
+ if (f.label !== void 0 && typeof f.label !== "string") {
799
+ errors.push(`${path}: "label" must be a string.`);
800
+ } else if (f.label === void 0) {
801
+ warnings.push(`${path}: no "label" defined; consider adding one for admin UX.`);
802
+ }
803
+ if (f.required !== void 0 && typeof f.required !== "boolean") {
804
+ errors.push(`${path}: "required" must be a boolean.`);
805
+ }
806
+ if (f.conditions !== void 0) {
807
+ validateConditions(f.conditions, `${path}.conditions`, errors);
808
+ }
809
+ if (f.conditionMode !== void 0 && f.conditionMode !== "AND" && f.conditionMode !== "OR") {
810
+ errors.push(`${path}: "conditionMode" must be "AND" or "OR".`);
811
+ }
812
+ if (f.validation !== void 0) {
813
+ if (!f.validation || typeof f.validation !== "object" || Array.isArray(f.validation)) {
814
+ errors.push(`${path}: "validation" must be an object.`);
815
+ } else {
816
+ validateValidationRules(
817
+ f.validation,
818
+ `${path}.validation`,
819
+ errors
820
+ );
821
+ }
822
+ }
823
+ if (f.ui !== void 0) {
824
+ if (!f.ui || typeof f.ui !== "object" || Array.isArray(f.ui)) {
825
+ errors.push(`${path}: "ui" must be an object.`);
826
+ } else {
827
+ const ui = f.ui;
828
+ if (ui.width !== void 0 && !["full", "half", "third", "quarter"].includes(ui.width)) {
829
+ errors.push(`${path}.ui.width: must be "full", "half", "third", or "quarter".`);
830
+ }
831
+ if (ui.order !== void 0 && typeof ui.order !== "number") {
832
+ errors.push(`${path}.ui.order: must be a number.`);
833
+ }
834
+ if (ui.collapsed !== void 0 && typeof ui.collapsed !== "boolean") {
835
+ errors.push(`${path}.ui.collapsed: must be a boolean.`);
836
+ }
837
+ }
838
+ }
839
+ if (type === "select" || type === "multiselect") {
840
+ if (!Array.isArray(f.options) || f.options.length === 0) {
841
+ errors.push(`${path}: "${type}" fields must have a non-empty "options" array.`);
842
+ } else {
843
+ ;
844
+ f.options.forEach((opt, i) => {
845
+ if (!opt || typeof opt !== "object") {
846
+ errors.push(`${path}.options[${i}]: must be an object.`);
847
+ return;
848
+ }
849
+ const o = opt;
850
+ if (typeof o.label !== "string" || !o.label.trim()) {
851
+ errors.push(`${path}.options[${i}]: "label" is required.`);
852
+ }
853
+ if (typeof o.value !== "string" || !o.value.trim()) {
854
+ errors.push(`${path}.options[${i}]: "value" is required.`);
855
+ }
856
+ });
857
+ }
858
+ }
859
+ if (type === "number") {
860
+ if (f.min !== void 0 && typeof f.min !== "number") {
861
+ errors.push(`${path}: "min" must be a number.`);
862
+ }
863
+ if (f.max !== void 0 && typeof f.max !== "number") {
864
+ errors.push(`${path}: "max" must be a number.`);
865
+ }
866
+ if (typeof f.min === "number" && typeof f.max === "number" && f.min > f.max) {
867
+ errors.push(`${path}: "min" (${f.min}) must be \xE2\u2030\xA4 "max" (${f.max}).`);
868
+ }
869
+ }
870
+ if (type === "text" || type === "textarea") {
871
+ if (f.minLength !== void 0 && typeof f.minLength !== "number") {
872
+ errors.push(`${path}: "minLength" must be a number.`);
873
+ }
874
+ if (f.maxLength !== void 0 && typeof f.maxLength !== "number") {
875
+ errors.push(`${path}: "maxLength" must be a number.`);
876
+ }
877
+ }
878
+ if (type === "relationship") {
879
+ if (typeof f.collection !== "string" || !f.collection.trim()) {
880
+ errors.push(`${path}: "relationship" fields require a non-empty "collection" string.`);
881
+ }
882
+ }
883
+ if (CONTAINER_TYPES.has(type)) {
884
+ if (!Array.isArray(f.fields) || f.fields.length === 0) {
885
+ errors.push(`${path}: "${type}" fields must have a non-empty "fields" array.`);
886
+ } else {
887
+ validateFields(f.fields, `${path}.fields`, errors, warnings);
888
+ }
889
+ if (type === "array") {
890
+ if (f.minRows !== void 0 && typeof f.minRows !== "number") {
891
+ errors.push(`${path}: "minRows" must be a number.`);
892
+ }
893
+ if (f.maxRows !== void 0 && typeof f.maxRows !== "number") {
894
+ errors.push(`${path}: "maxRows" must be a number.`);
895
+ }
896
+ }
897
+ }
898
+ if (type === "blocks") {
899
+ if (f.allowedBlocks !== void 0) {
900
+ if (!Array.isArray(f.allowedBlocks)) {
901
+ errors.push(`${path}: "allowedBlocks" must be an array.`);
902
+ } else {
903
+ ;
904
+ f.allowedBlocks.forEach((slug, i) => {
905
+ if (typeof slug !== "string" || !slug.trim()) {
906
+ errors.push(`${path}.allowedBlocks[${i}]: must be a non-empty string.`);
907
+ }
908
+ });
909
+ }
910
+ }
911
+ if (f.minBlocks !== void 0 && typeof f.minBlocks !== "number") {
912
+ errors.push(`${path}: "minBlocks" must be a number.`);
913
+ }
914
+ if (f.maxBlocks !== void 0 && typeof f.maxBlocks !== "number") {
915
+ errors.push(`${path}: "maxBlocks" must be a number.`);
916
+ }
917
+ if (typeof f.minBlocks === "number" && typeof f.maxBlocks === "number" && f.minBlocks > f.maxBlocks) {
918
+ errors.push(`${path}: "minBlocks" (${f.minBlocks}) must be \xE2\u2030\xA4 "maxBlocks" (${f.maxBlocks}).`);
919
+ }
920
+ }
921
+ }
922
+ function validateFields(fields, path, errors, warnings) {
923
+ const names = /* @__PURE__ */ new Set();
924
+ fields.forEach((field, index) => {
925
+ const fieldPath = `${path}[${index}]`;
926
+ validateField(field, fieldPath, errors, warnings);
927
+ const f = field;
928
+ if (typeof f.name === "string" && f.name) {
929
+ if (names.has(f.name)) {
930
+ errors.push(`${path}: duplicate field name "${f.name}".`);
931
+ }
932
+ names.add(f.name);
933
+ }
934
+ });
935
+ }
936
+ function validateBlockSchema(schema) {
937
+ const errors = [];
938
+ const warnings = [];
939
+ if (!schema || typeof schema !== "object" || Array.isArray(schema)) {
940
+ return {
941
+ valid: false,
942
+ errors: ['Schema must be a non-array object with a "fields" property.'],
943
+ warnings: []
944
+ };
945
+ }
946
+ const s = schema;
947
+ if (!Array.isArray(s.fields)) {
948
+ errors.push('Schema must have a "fields" array.');
949
+ } else if (s.fields.length === 0) {
950
+ warnings.push("Schema has no fields defined.");
951
+ } else {
952
+ validateFields(s.fields, "schema.fields", errors, warnings);
953
+ }
954
+ if (s.layout !== void 0 && !["default", "sidebar", "tabs"].includes(s.layout)) {
955
+ errors.push(`schema.layout must be one of: "default", "sidebar", "tabs".`);
956
+ }
957
+ return { valid: errors.length === 0, errors, warnings };
958
+ }
959
+
960
+ // src/builder/saveSchema.ts
961
+ async function saveSchemaLocally(payload, request) {
962
+ const { blockSlug, name, description, category, schema: rawSchema, changelog } = request;
963
+ const schema = normaliseSchema(rawSchema);
964
+ if (!schema.fields?.length) {
965
+ return {
966
+ success: false,
967
+ definitionId: "",
968
+ versionId: "",
969
+ versionNumber: 0,
970
+ errors: [`Block "${blockSlug}" schema has no fields. Schemas must be imported from a server-safe module (not a "use client" file).`],
971
+ warnings: []
972
+ };
973
+ }
974
+ const validation = validateBlockSchema(schema);
975
+ if (!validation.valid) {
976
+ return {
977
+ success: false,
978
+ definitionId: "",
979
+ versionId: "",
980
+ versionNumber: 0,
981
+ errors: validation.errors,
982
+ warnings: validation.warnings
983
+ };
984
+ }
985
+ try {
986
+ let definitionId;
987
+ const existing = await payload.find({
988
+ collection: "block-definitions",
989
+ where: { slug: { equals: blockSlug } },
990
+ limit: 1
991
+ });
992
+ if (existing.docs.length > 0) {
993
+ definitionId = existing.docs[0].id;
994
+ const updates = {};
995
+ if (description !== void 0) updates.description = description;
996
+ if (category !== void 0) updates.category = category;
997
+ if (name !== void 0) updates.name = name;
998
+ if (Object.keys(updates).length > 0) {
999
+ await payload.update({
1000
+ collection: "block-definitions",
1001
+ id: definitionId,
1002
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1003
+ data: updates
1004
+ });
1005
+ }
1006
+ } else {
1007
+ if (!name) {
1008
+ return {
1009
+ success: false,
1010
+ definitionId: "",
1011
+ versionId: "",
1012
+ versionNumber: 0,
1013
+ errors: [`Block definition "${blockSlug}" not found. Provide a "name" to create it.`],
1014
+ warnings: validation.warnings
1015
+ };
1016
+ }
1017
+ const created = await payload.create({
1018
+ collection: "block-definitions",
1019
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1020
+ data: {
1021
+ slug: blockSlug,
1022
+ name,
1023
+ description
1024
+ }
1025
+ });
1026
+ definitionId = created.id;
1027
+ }
1028
+ const existingVersions = await payload.find({
1029
+ collection: "block-definition-versions",
1030
+ where: { blockDefinition: { equals: definitionId } },
1031
+ limit: 0
1032
+ });
1033
+ const versionNumber = existingVersions.totalDocs + 1;
1034
+ const version = await payload.create({
1035
+ collection: "block-definition-versions",
1036
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1037
+ data: {
1038
+ blockDefinition: definitionId,
1039
+ versionNumber,
1040
+ label: `v${versionNumber}`,
1041
+ schema,
1042
+ changelog: changelog ?? "Created via Block Builder"
1043
+ }
1044
+ });
1045
+ await payload.update({
1046
+ collection: "block-definitions",
1047
+ id: definitionId,
1048
+ data: { currentVersion: version.id }
1049
+ });
1050
+ return {
1051
+ success: true,
1052
+ definitionId: String(definitionId),
1053
+ versionId: String(version.id),
1054
+ versionNumber: version.versionNumber ?? 0,
1055
+ warnings: validation.warnings
1056
+ };
1057
+ } catch (err) {
1058
+ const message = err instanceof Error ? err.message : String(err);
1059
+ return {
1060
+ success: false,
1061
+ definitionId: "",
1062
+ versionId: "",
1063
+ versionNumber: 0,
1064
+ errors: [`Unexpected error: ${message}`],
1065
+ warnings: validation.warnings
1066
+ };
1067
+ }
1068
+ }
1069
+
1070
+ // src/endpoints/save.ts
1071
+ var saveEndpoint = async (req) => {
1072
+ if (!req.user) {
1073
+ return Response.json({ error: "Unauthorized" }, { status: 401 });
1074
+ }
1075
+ let body;
1076
+ try {
1077
+ if (!req.json) return Response.json({ error: "No JSON parser available" }, { status: 500 });
1078
+ body = await req.json();
1079
+ } catch {
1080
+ return Response.json({ error: "Invalid JSON body" }, { status: 400 });
1081
+ }
1082
+ if (!body.blockSlug) {
1083
+ return Response.json({ error: "blockSlug is required" }, { status: 400 });
1084
+ }
1085
+ const result = await saveSchemaLocally(req.payload, body);
1086
+ return Response.json(result, { status: result.success ? 200 : 422 });
1087
+ };
1088
+
1089
+ // src/endpoints/versions.ts
1090
+ var versionsEndpoint = async (req) => {
1091
+ if (!req.user) {
1092
+ return Response.json({ error: "Unauthorized" }, { status: 401 });
1093
+ }
1094
+ const slug = req.routeParams?.slug;
1095
+ if (!slug) {
1096
+ return Response.json({ error: "Slug is required" }, { status: 400 });
1097
+ }
1098
+ const defResult = await req.payload.find({
1099
+ collection: "block-definitions",
1100
+ where: { slug: { equals: slug } },
1101
+ depth: 1,
1102
+ limit: 1
1103
+ });
1104
+ const def = defResult.docs[0];
1105
+ if (!def) {
1106
+ return Response.json({ error: `Block definition "${slug}" not found` }, { status: 404 });
1107
+ }
1108
+ const currentVersionId = def.currentVersion && typeof def.currentVersion === "object" ? String(def.currentVersion.id) : typeof def.currentVersion === "string" || typeof def.currentVersion === "number" ? String(def.currentVersion) : null;
1109
+ const versionsResult = await req.payload.find({
1110
+ collection: "block-definition-versions",
1111
+ where: { blockDefinition: { equals: def.id } },
1112
+ sort: "-versionNumber",
1113
+ depth: 0,
1114
+ limit: 100
1115
+ });
1116
+ const versions = versionsResult.docs.map((v) => {
1117
+ const id = String(v.id);
1118
+ return {
1119
+ id,
1120
+ versionNumber: v.versionNumber,
1121
+ label: v.label ?? `v${v.versionNumber}`,
1122
+ changelog: v.changelog ?? "",
1123
+ createdAt: v.createdAt,
1124
+ isCurrent: id === currentVersionId
1125
+ };
1126
+ });
1127
+ return Response.json({ versions });
1128
+ };
1129
+
1130
+ // src/plugin.ts
1131
+ var dynamicBlocksPlugin = (options = {}) => {
1132
+ return (incomingConfig) => {
1133
+ const {
1134
+ enabled = true,
1135
+ collections: targetCollections = [],
1136
+ fieldName = "dbLayout",
1137
+ tabLabel = "DB Layout"
1138
+ } = options;
1139
+ if (!enabled) return incomingConfig;
1140
+ const config = { ...incomingConfig };
1141
+ config.collections = [
1142
+ ...config.collections ?? [],
1143
+ BlockDefinitions,
1144
+ BlockDefinitionVersions
1145
+ ];
1146
+ config.endpoints = [
1147
+ ...config.endpoints ?? [],
1148
+ {
1149
+ path: "/block-builder/generate",
1150
+ method: "post",
1151
+ handler: generateEndpoint
1152
+ },
1153
+ {
1154
+ path: "/block-builder/load/:slug",
1155
+ method: "get",
1156
+ handler: loadEndpoint
1157
+ },
1158
+ {
1159
+ path: "/blocks/save",
1160
+ method: "post",
1161
+ handler: saveEndpoint
1162
+ },
1163
+ {
1164
+ path: "/block-builder/versions/:slug",
1165
+ method: "get",
1166
+ handler: versionsEndpoint
1167
+ }
1168
+ ];
1169
+ if (targetCollections.length > 0) {
1170
+ config.collections = config.collections.map((collection) => {
1171
+ if (!targetCollections.includes(collection.slug)) return collection;
1172
+ const layoutTab = dbLayoutField(fieldName, tabLabel);
1173
+ const existingTabsIdx = collection.fields.findIndex((f) => f.type === "tabs");
1174
+ if (existingTabsIdx !== -1) {
1175
+ const existingTabs = collection.fields[existingTabsIdx];
1176
+ return {
1177
+ ...collection,
1178
+ fields: collection.fields.map(
1179
+ (f, i) => i === existingTabsIdx ? {
1180
+ ...existingTabs,
1181
+ tabs: [...existingTabs.tabs, layoutTab]
1182
+ } : f
1183
+ )
1184
+ };
1185
+ }
1186
+ return {
1187
+ ...collection,
1188
+ fields: [
1189
+ ...collection.fields,
1190
+ {
1191
+ type: "tabs",
1192
+ tabs: [layoutTab]
1193
+ }
1194
+ ]
1195
+ };
1196
+ });
1197
+ config.collections = config.collections.map((collection) => {
1198
+ if (!targetCollections.includes(collection.slug)) return collection;
1199
+ const existingHooks = collection.hooks?.beforeChange ?? [];
1200
+ return {
1201
+ ...collection,
1202
+ hooks: {
1203
+ ...collection.hooks,
1204
+ beforeChange: [
1205
+ ...existingHooks,
1206
+ ({ data }) => {
1207
+ if (Array.isArray(data[fieldName])) {
1208
+ data[fieldName] = data[fieldName].map(
1209
+ (row) => row.instanceId ? row : { ...row, instanceId: (0, import_crypto.randomUUID)() }
1210
+ );
1211
+ }
1212
+ return data;
1213
+ }
1214
+ ]
1215
+ }
1216
+ };
1217
+ });
1218
+ }
1219
+ return config;
1220
+ };
1221
+ };
1222
+ // Annotate the CommonJS export names for ESM import in node:
1223
+ 0 && (module.exports = {
1224
+ BlockDefinitionVersions,
1225
+ BlockDefinitions,
1226
+ dbLayoutField,
1227
+ dynamicBlocksPlugin
1228
+ });