strapi-relation-names 1.2.1 → 1.3.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.
Files changed (33) hide show
  1. package/README.md +17 -42
  2. package/dist/{admin/SettingsPage-BebR4ghK.js → _chunks/SettingsPage-BMwx-BuP.js} +122 -48
  3. package/dist/{admin/SettingsPage-DFZP5CZs.mjs → _chunks/SettingsPage-D8leItKF.mjs} +122 -48
  4. package/dist/{admin/index-CiS6cuI9.mjs → _chunks/index-BeLPoAiU.mjs} +1 -1
  5. package/dist/{admin/index-DfSAe961.js → _chunks/index-COuIED98.js} +1 -1
  6. package/dist/admin/index.js +2 -3
  7. package/dist/admin/index.mjs +1 -1
  8. package/dist/admin/src/index.d.ts +1 -1
  9. package/dist/admin/src/pages/SettingsPage.d.ts +1 -0
  10. package/dist/admin/src/utils/template.d.ts +1 -14
  11. package/dist/server/index.js +279 -130
  12. package/dist/server/index.mjs +278 -128
  13. package/dist/server/src/bootstrap.d.ts +1 -1
  14. package/dist/server/src/config/index.d.ts +2 -0
  15. package/dist/server/src/controllers/index.d.ts +1 -1
  16. package/dist/server/src/controllers/settings.d.ts +1 -1
  17. package/dist/server/src/destroy.d.ts +1 -1
  18. package/dist/server/src/index.d.ts +20 -11
  19. package/dist/server/src/register.d.ts +1 -1
  20. package/dist/server/src/services/index.d.ts +8 -7
  21. package/dist/server/src/services/relation-labels.d.ts +6 -9
  22. package/dist/server/src/services/settings.d.ts +2 -2
  23. package/dist/server/src/services/utils/relation-hydration.d.ts +7 -0
  24. package/dist/server/src/services/utils/relation-labels.d.ts +4 -22
  25. package/dist/server/src/services/utils/relation-runtime.d.ts +6 -0
  26. package/dist/server/src/services/utils/relation-values.d.ts +1 -1
  27. package/dist/shared/records.d.ts +1 -0
  28. package/dist/shared/template.d.ts +55 -0
  29. package/package.json +28 -28
  30. /package/dist/{admin → _chunks}/de-Bc81LkbY.mjs +0 -0
  31. /package/dist/{admin → _chunks}/de-CsBvOsWR.js +0 -0
  32. /package/dist/{admin → _chunks}/en-Bk-HIXNd.mjs +0 -0
  33. /package/dist/{admin → _chunks}/en-DT4g6X2H.js +0 -0
@@ -1,12 +1,11 @@
1
1
  "use strict";
2
- Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
3
2
  const bootstrap = ({ strapi }) => {
4
3
  };
5
4
  const destroy = ({ strapi }) => {
6
5
  };
7
- const PLUGIN_ID$1 = "strapi-relation-names";
6
+ const PLUGIN_ID = "strapi-relation-names";
8
7
  const register = ({ strapi }) => {
9
- const relationLabels2 = strapi.plugin(PLUGIN_ID$1).service("relation-labels");
8
+ const relationLabels2 = strapi.plugin(PLUGIN_ID).service("relation-labels");
10
9
  const controllers2 = strapi.get("controllers");
11
10
  controllers2.extend("plugin::content-manager.relations", (controller) => ({
12
11
  ...controller,
@@ -58,6 +57,7 @@ const register = ({ strapi }) => {
58
57
  await decorateConfiguration(ctx, ctx.params.uid);
59
58
  },
60
59
  async updateContentTypeConfiguration(ctx, next) {
60
+ ctx.request.body = relationLabels2.sanitizeConfigurationUpdate(ctx.request.body);
61
61
  await controller.updateContentTypeConfiguration(ctx, next);
62
62
  await decorateConfiguration(ctx, ctx.params.uid);
63
63
  }
@@ -74,12 +74,28 @@ const register = ({ strapi }) => {
74
74
  }
75
75
  }));
76
76
  };
77
+ const isOptionalString = (value) => value === void 0 || typeof value === "string";
77
78
  const validateConfig = (config2) => {
78
- if (!config2 || typeof config2 !== "object" || Array.isArray(config2) || !Array.isArray(config2.collections) || !config2.collections.every(
79
- (collection) => typeof collection === "string"
80
- )) {
79
+ if (!config2 || typeof config2 !== "object" || Array.isArray(config2)) {
80
+ throw new Error("The collections config must be an array of strings");
81
+ }
82
+ const typedConfig = config2;
83
+ if (!Array.isArray(typedConfig.collections) || !typedConfig.collections.every((collection) => typeof collection === "string")) {
81
84
  throw new Error("The collections config must be an array of strings");
82
85
  }
86
+ if (!isOptionalString(typedConfig.locale)) {
87
+ throw new Error("The locale config must be a string");
88
+ }
89
+ if (!isOptionalString(typedConfig.timeZone)) {
90
+ throw new Error("The timeZone config must be a string");
91
+ }
92
+ if (typedConfig.timeZone) {
93
+ try {
94
+ new Intl.DateTimeFormat(void 0, { timeZone: typedConfig.timeZone });
95
+ } catch {
96
+ throw new Error("The timeZone config must be a valid IANA timezone");
97
+ }
98
+ }
83
99
  };
84
100
  const config = {
85
101
  default: {
@@ -90,7 +106,7 @@ const config = {
90
106
  const contentTypes = {};
91
107
  const settings$1 = ({ strapi }) => ({
92
108
  async find(ctx) {
93
- const data = await strapi.plugin("strapi-relation-names").service("settings").get();
109
+ const data = await strapi.plugin(PLUGIN_ID).service("settings").get();
94
110
  ctx.body = { data };
95
111
  },
96
112
  async update(ctx) {
@@ -98,7 +114,7 @@ const settings$1 = ({ strapi }) => ({
98
114
  if (!body || typeof body !== "object" || Array.isArray(body)) {
99
115
  return ctx.badRequest("Settings must be an object");
100
116
  }
101
- const data = await strapi.plugin("strapi-relation-names").service("settings").set(body);
117
+ const data = await strapi.plugin(PLUGIN_ID).service("settings").set(body);
102
118
  ctx.body = { data };
103
119
  }
104
120
  });
@@ -136,7 +152,8 @@ const routes = {
136
152
  "content-api": contentAPIRoutes,
137
153
  admin: adminAPIRoutes
138
154
  };
139
- const SCALAR_TYPES = /* @__PURE__ */ new Set([
155
+ const isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
156
+ const FIELD_TYPES = /* @__PURE__ */ new Set([
140
157
  "string",
141
158
  "text",
142
159
  "email",
@@ -151,30 +168,81 @@ const SCALAR_TYPES = /* @__PURE__ */ new Set([
151
168
  "time",
152
169
  "boolean"
153
170
  ]);
154
- const SYSTEM_SCALAR_FIELDS = /* @__PURE__ */ new Set([
155
- "id",
156
- "documentId",
157
- "createdAt",
158
- "updatedAt",
159
- "publishedAt",
160
- "locale",
161
- "status"
162
- ]);
163
- const PLACEHOLDER_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*$/;
164
- const isRecord$1 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
171
+ const DATE_TYPES = /* @__PURE__ */ new Set(["date", "datetime"]);
172
+ const SYSTEM_SCALAR_TYPES = {
173
+ id: "integer",
174
+ documentId: "string",
175
+ createdAt: "datetime",
176
+ updatedAt: "datetime",
177
+ publishedAt: "datetime",
178
+ locale: "string",
179
+ status: "string"
180
+ };
181
+ const IDENTIFIER_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
182
+ const PATH_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*$/;
183
+ const getNestedSchemaUid = (attribute) => {
184
+ if (attribute.type === "component") {
185
+ return attribute.component;
186
+ }
187
+ if (attribute.type === "relation") {
188
+ return attribute.targetModel ?? attribute.target;
189
+ }
190
+ return void 0;
191
+ };
192
+ const getDate = (value) => {
193
+ if (value instanceof Date) {
194
+ return Number.isNaN(value.getTime()) ? void 0 : value;
195
+ }
196
+ if (typeof value !== "string" && typeof value !== "number") {
197
+ return void 0;
198
+ }
199
+ const date = new Date(value);
200
+ return Number.isNaN(date.getTime()) ? void 0 : date;
201
+ };
202
+ const isDateOnly = (value) => typeof value === "string" && /^\d{4}-\d{2}-\d{2}$/.test(value);
203
+ const formatDate = (value, options, context, preserveDateOnly) => {
204
+ const date = getDate(value);
205
+ if (!date) {
206
+ return "";
207
+ }
208
+ const timeZone = preserveDateOnly && isDateOnly(value) ? "UTC" : context.timeZone;
209
+ return new Intl.DateTimeFormat(context.locale, {
210
+ ...options,
211
+ ...timeZone ? { timeZone } : {}
212
+ }).format(date);
213
+ };
214
+ const TRANSFORMS = {
215
+ toLocalDate: {
216
+ accepts: (fieldType) => DATE_TYPES.has(fieldType ?? ""),
217
+ apply: (value, context) => formatDate(value, { dateStyle: "short" }, context, true)
218
+ },
219
+ toLocalDateTime: {
220
+ accepts: (fieldType) => fieldType === "datetime",
221
+ apply: (value, context) => formatDate(value, { dateStyle: "short", timeStyle: "short" }, context, false)
222
+ }
223
+ };
165
224
  const getPathValue = (value, path) => {
166
225
  return path.split(".").reduce((current, segment) => {
167
226
  if (Array.isArray(current)) {
168
- return current.map((item) => isRecord$1(item) ? item[segment] : void 0).filter((item) => item !== void 0);
227
+ return current.map((item) => isRecord(item) ? item[segment] : void 0).filter((item) => item !== void 0);
169
228
  }
170
- return isRecord$1(current) ? current[segment] : void 0;
229
+ return isRecord(current) ? current[segment] : void 0;
171
230
  }, value);
172
231
  };
173
- const parseTemplate = (template) => {
232
+ const parseExpression = (value) => {
233
+ const parts = value.split("|").map((part) => part.trim());
234
+ const [path, ...transforms] = parts;
235
+ if (!path || !PATH_PATTERN.test(path) || transforms.some((name) => !IDENTIFIER_PATTERN.test(name))) {
236
+ return null;
237
+ }
238
+ return { path, transforms };
239
+ };
240
+ const parseTemplateAst = (template) => {
174
241
  if (!template.trim()) {
175
242
  return null;
176
243
  }
177
- const placeholders = [];
244
+ const tokens = [];
245
+ const expressions = [];
178
246
  let cursor = 0;
179
247
  while (cursor < template.length) {
180
248
  const openingBrace = template.indexOf("{", cursor);
@@ -183,69 +251,98 @@ const parseTemplate = (template) => {
183
251
  return null;
184
252
  }
185
253
  if (openingBrace === -1) {
254
+ tokens.push({ type: "text", value: template.slice(cursor) });
186
255
  break;
187
256
  }
257
+ if (openingBrace > cursor) {
258
+ tokens.push({ type: "text", value: template.slice(cursor, openingBrace) });
259
+ }
188
260
  const end = template.indexOf("}", openingBrace + 1);
189
261
  if (end === -1) {
190
262
  return null;
191
263
  }
192
- const name = template.slice(openingBrace + 1, end);
193
- if (!PLACEHOLDER_PATTERN.test(name)) {
264
+ const expression = parseExpression(template.slice(openingBrace + 1, end));
265
+ if (!expression) {
194
266
  return null;
195
267
  }
196
- placeholders.push(name);
268
+ tokens.push({ type: "expression", expression });
269
+ expressions.push(expression);
197
270
  cursor = end + 1;
198
271
  }
199
- return placeholders.length > 0 ? placeholders : null;
272
+ return expressions.length > 0 ? { tokens, expressions } : null;
273
+ };
274
+ const parseTemplate = (template) => {
275
+ const ast = parseTemplateAst(template);
276
+ return ast?.expressions.map(({ path }) => path) ?? null;
277
+ };
278
+ const resolvePathType = (path, targetSchema, resolveSchema) => {
279
+ const segments = path.split(".");
280
+ let schema = targetSchema;
281
+ for (const [index2, segment] of segments.entries()) {
282
+ const isLastSegment = index2 === segments.length - 1;
283
+ if (index2 === 0 && isLastSegment && SYSTEM_SCALAR_TYPES[segment]) {
284
+ return SYSTEM_SCALAR_TYPES[segment];
285
+ }
286
+ const attribute = schema.attributes?.[segment];
287
+ if (!attribute || attribute.private === true) {
288
+ return void 0;
289
+ }
290
+ if (isLastSegment) {
291
+ return FIELD_TYPES.has(attribute.type ?? "") ? attribute.type : void 0;
292
+ }
293
+ const nestedSchemaUid = getNestedSchemaUid(attribute);
294
+ if (!nestedSchemaUid || !resolveSchema) {
295
+ return void 0;
296
+ }
297
+ const nestedSchema = resolveSchema(nestedSchemaUid);
298
+ if (!nestedSchema) {
299
+ return void 0;
300
+ }
301
+ schema = nestedSchema;
302
+ }
303
+ return void 0;
200
304
  };
201
305
  const compileTemplate = (template, targetSchema, resolveSchema) => {
202
- const placeholders = parseTemplate(template);
203
- if (!placeholders || !targetSchema) {
306
+ const ast = parseTemplateAst(template);
307
+ if (!ast || !targetSchema) {
204
308
  return null;
205
309
  }
206
- const valid = placeholders.every((path) => {
207
- const segments = path.split(".");
208
- let schema = targetSchema;
209
- for (const [index2, segment] of segments.entries()) {
210
- const attribute = schema.attributes?.[segment];
211
- const isLastSegment = index2 === segments.length - 1;
212
- if (index2 === 0 && isLastSegment && SYSTEM_SCALAR_FIELDS.has(segment)) {
213
- return true;
214
- }
215
- if (!attribute || attribute.private === true) {
216
- return false;
217
- }
218
- if (isLastSegment) {
219
- return SCALAR_TYPES.has(attribute.type ?? "");
220
- }
221
- const nestedSchemaUid = attribute.type === "component" ? attribute.component : attribute.type === "relation" ? attribute.target : void 0;
222
- if (!nestedSchemaUid || !resolveSchema) {
223
- return false;
224
- }
225
- schema = resolveSchema(nestedSchemaUid);
226
- if (!schema) {
227
- return false;
228
- }
229
- }
230
- return false;
310
+ const valid = ast.expressions.every(({ path, transforms }) => {
311
+ const fieldType = resolvePathType(path, targetSchema, resolveSchema);
312
+ return Boolean(fieldType && transforms.every((name) => TRANSFORMS[name]?.accepts(fieldType)));
231
313
  });
232
314
  if (!valid) {
233
315
  return null;
234
316
  }
235
317
  return {
318
+ ...ast,
236
319
  template,
237
- placeholders,
238
- displayField: placeholders[0].split(".").slice(-1)[0] ?? placeholders[0]
320
+ placeholders: ast.expressions.map(({ path }) => path),
321
+ displayField: ast.expressions[0].path.split(".").slice(-1)[0] ?? ast.expressions[0].path
239
322
  };
240
323
  };
241
- const renderTemplate = (compiled, values) => {
242
- const rendered = compiled.template.replace(
243
- /\{([A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*)\}/g,
244
- (_match, name) => {
245
- const value = getPathValue(values, name);
246
- return value === null || value === void 0 ? "" : String(value);
247
- }
248
- );
324
+ const applyTransform = (value, name, context) => {
325
+ const transform = TRANSFORMS[name];
326
+ if (!transform) {
327
+ return "";
328
+ }
329
+ if (Array.isArray(value)) {
330
+ return value.map((item) => applyTransform(item, name, context));
331
+ }
332
+ return transform.apply(value, context);
333
+ };
334
+ const renderTemplate = (compiled, values, context = compiled.transformContext ?? {}) => {
335
+ const tokens = compiled.tokens ?? parseTemplateAst(compiled.template)?.tokens ?? [];
336
+ const rendered = tokens.map((token) => {
337
+ if (token.type === "text") {
338
+ return token.value;
339
+ }
340
+ const expressionValue = token.expression.transforms.reduce(
341
+ (value, name) => applyTransform(value, name, context),
342
+ getPathValue(values, token.expression.path)
343
+ );
344
+ return expressionValue === null || expressionValue === void 0 ? "" : String(expressionValue);
345
+ }).join("");
249
346
  return rendered.trim();
250
347
  };
251
348
  const getIdentity = (value, modelType) => modelType === "component" ? value.id : value.documentId ?? value.id;
@@ -267,6 +364,11 @@ const matchesRelation = (source, candidate, targetModelType) => {
267
364
  const applyLabel = (value, hydrated, runtime) => {
268
365
  const label = renderTemplate(runtime, hydrated);
269
366
  if (label) {
367
+ const isNestedTemplate = runtime.placeholders.some((placeholder) => placeholder.includes("."));
368
+ const isIdentityField = runtime.displayField === "id" || runtime.displayField === "documentId";
369
+ if (isNestedTemplate && isIdentityField) {
370
+ return { ...value, label };
371
+ }
270
372
  return { ...value, [runtime.displayField]: label };
271
373
  }
272
374
  if (runtime.displayField === runtime.originalMainField) {
@@ -278,60 +380,15 @@ const applyLabel = (value, hydrated, runtime) => {
278
380
  };
279
381
  const relationValues = (value) => {
280
382
  if (Array.isArray(value)) {
281
- return value.filter(isRecord$1);
383
+ return value.filter(isRecord);
282
384
  }
283
- return isRecord$1(value) ? [value] : [];
385
+ return isRecord(value) ? [value] : [];
284
386
  };
285
387
  const replaceRelationValue = (original, values, hydrated, runtime) => {
286
388
  const decorated = values.map((value) => applyLabel(value, hydrated.get(value) ?? value, runtime));
287
389
  return Array.isArray(original) ? decorated : decorated[0] ?? original;
288
390
  };
289
391
  const isCollectionEnabled = (collections, uid) => collections.length === 0 || collections.includes(uid);
290
- const getOriginalMainField = async (strapi, sourceSchema, targetSchema, targetField, userAbility) => {
291
- const serviceName = sourceSchema.modelType === "component" ? "components" : "content-types";
292
- const configuration = await strapi.plugin("content-manager").service(serviceName).findConfiguration(sourceSchema);
293
- const configuredMainField = configuration?.metadatas?.[targetField]?.edit?.mainField;
294
- if (typeof configuredMainField !== "string" || configuredMainField === "documentId" || configuredMainField === "id") {
295
- return configuredMainField === "documentId" ? "documentId" : "id";
296
- }
297
- const permissionChecker = strapi.plugin("content-manager").service("permission-checker").create({ userAbility, model: targetSchema.uid });
298
- const attribute = targetSchema.attributes?.[configuredMainField];
299
- if (!attribute || attribute.private === true || permissionChecker.cannot.read(null, configuredMainField)) {
300
- return "documentId";
301
- }
302
- return configuredMainField;
303
- };
304
- const getRuntime = async (strapi, settings2, sourceUid, targetField, userAbility) => {
305
- const sourceSchema = strapi.getModel(sourceUid);
306
- const attribute = sourceSchema?.attributes?.[targetField];
307
- if (!sourceSchema || !attribute || attribute.type !== "relation" || !attribute.target) {
308
- return null;
309
- }
310
- const template = settings2.relations[sourceUid]?.[targetField];
311
- const targetSchema = strapi.getModel(attribute.target);
312
- const compiled = typeof template === "string" ? compileTemplate(
313
- template,
314
- targetSchema,
315
- (uid) => strapi.getModel(uid)
316
- ) : null;
317
- if (!compiled || !targetSchema) {
318
- return null;
319
- }
320
- const originalMainField = await getOriginalMainField(
321
- strapi,
322
- sourceSchema,
323
- targetSchema,
324
- targetField,
325
- userAbility
326
- );
327
- return {
328
- ...compiled,
329
- displayField: compiled.placeholders.some((placeholder) => placeholder.includes(".")) ? originalMainField : compiled.displayField,
330
- sourceUid,
331
- targetUid: targetSchema.uid,
332
- originalMainField
333
- };
334
- };
335
392
  const buildPopulate = (placeholders) => {
336
393
  const populate = {};
337
394
  for (const placeholder of placeholders) {
@@ -404,8 +461,61 @@ const hydrateValues = async (strapi, ctx, runtime, values) => {
404
461
  return new Map(values.map((value) => [value, value]));
405
462
  }
406
463
  };
464
+ const getOriginalMainField = async (strapi, sourceSchema, targetSchema, targetField, userAbility) => {
465
+ const serviceName = sourceSchema.modelType === "component" ? "components" : "content-types";
466
+ const configuration = await strapi.plugin("content-manager").service(serviceName).findConfiguration(sourceSchema);
467
+ const configuredMainField = configuration?.metadatas?.[targetField]?.edit?.mainField;
468
+ if (typeof configuredMainField !== "string" || configuredMainField === "documentId" || configuredMainField === "id") {
469
+ return configuredMainField === "documentId" ? "documentId" : "id";
470
+ }
471
+ const permissionChecker = strapi.plugin("content-manager").service("permission-checker").create({ userAbility, model: targetSchema.uid });
472
+ const attribute = targetSchema.attributes?.[configuredMainField];
473
+ if (!attribute || attribute.private === true || permissionChecker.cannot.read(null, configuredMainField)) {
474
+ return "documentId";
475
+ }
476
+ return configuredMainField;
477
+ };
478
+ const getRuntime = async (strapi, settings2, sourceUid, targetField, userAbility, transformContext) => {
479
+ const sourceSchema = strapi.getModel(sourceUid);
480
+ const attribute = sourceSchema?.attributes?.[targetField];
481
+ if (!sourceSchema || !attribute || attribute.type !== "relation" || !attribute.target) {
482
+ return null;
483
+ }
484
+ const template = settings2.relations[sourceUid]?.[targetField];
485
+ const targetSchema = strapi.getModel(attribute.target);
486
+ const compiled = typeof template === "string" ? compileTemplate(
487
+ template,
488
+ targetSchema,
489
+ (uid) => strapi.getModel(uid)
490
+ ) : null;
491
+ if (!compiled || !targetSchema) {
492
+ return null;
493
+ }
494
+ const originalMainField = await getOriginalMainField(
495
+ strapi,
496
+ sourceSchema,
497
+ targetSchema,
498
+ targetField,
499
+ userAbility
500
+ );
501
+ return {
502
+ ...compiled,
503
+ transformContext,
504
+ displayField: compiled.placeholders.some((placeholder) => placeholder.includes(".")) ? originalMainField : compiled.displayField,
505
+ sourceUid,
506
+ targetUid: targetSchema.uid,
507
+ originalMainField
508
+ };
509
+ };
510
+ const getTransformContext = (strapi) => {
511
+ const config2 = strapi.config.get("plugin::strapi-relation-names");
512
+ return {
513
+ locale: typeof config2?.locale === "string" ? config2.locale : void 0,
514
+ timeZone: typeof config2?.timeZone === "string" ? config2.timeZone : void 0
515
+ };
516
+ };
407
517
  const relationLabels = ({ strapi }) => {
408
- const getSettings = () => strapi.plugin("strapi-relation-names").service("settings").get();
518
+ const getSettings = () => strapi.plugin(PLUGIN_ID).service("settings").get();
409
519
  const decorateRelationResults = async (ctx, sourceUid, targetField, results) => {
410
520
  const settings2 = await getSettings();
411
521
  if (!isCollectionEnabled(settings2.collections, sourceUid)) {
@@ -416,26 +526,27 @@ const relationLabels = ({ strapi }) => {
416
526
  settings2,
417
527
  sourceUid,
418
528
  targetField,
419
- ctx.state.userAbility
529
+ ctx.state.userAbility,
530
+ getTransformContext(strapi)
420
531
  );
421
532
  if (!runtime) {
422
533
  return results;
423
534
  }
424
- const values = results.filter(isRecord$1);
535
+ const values = results.filter(isRecord);
425
536
  const hydrated = await hydrateValues(strapi, ctx, runtime, values);
426
537
  return results.map(
427
- (result) => isRecord$1(result) ? applyLabel(result, hydrated.get(result) ?? result, runtime) : result
538
+ (result) => isRecord(result) ? applyLabel(result, hydrated.get(result) ?? result, runtime) : result
428
539
  );
429
540
  };
430
541
  const decorateConfiguration = async (configuration, sourceUid) => {
431
- if (!configuration || !isRecord$1(configuration.metadatas)) {
542
+ if (!configuration || !isRecord(configuration.metadatas)) {
432
543
  return configuration;
433
544
  }
434
545
  const settings2 = await getSettings();
435
546
  const metadatas = { ...configuration.metadatas };
436
547
  const relationNames = {};
548
+ const sourceSchema = strapi.getModel(sourceUid);
437
549
  for (const [fieldName, metadata] of Object.entries(metadatas)) {
438
- const sourceSchema = strapi.getModel(sourceUid);
439
550
  const attribute = sourceSchema?.attributes?.[fieldName];
440
551
  if (!attribute || attribute.type !== "relation" || !attribute.target) {
441
552
  continue;
@@ -446,12 +557,16 @@ const relationLabels = ({ strapi }) => {
446
557
  targetSchema,
447
558
  (uid) => strapi.getModel(uid)
448
559
  );
449
- if (!compiled || !isRecord$1(metadata)) {
560
+ if (!compiled || !isRecord(metadata)) {
450
561
  continue;
451
562
  }
452
- const editMetadata = isRecord$1(metadata.edit) ? metadata.edit : {};
563
+ const editMetadata = isRecord(metadata.edit) ? metadata.edit : {};
453
564
  const configuredMainField = typeof editMetadata.mainField === "string" ? editMetadata.mainField : "id";
454
- const displayField = compiled.placeholders.some((placeholder) => placeholder.includes(".")) ? configuredMainField : compiled.displayField;
565
+ const isNestedTemplate = compiled.placeholders.some(
566
+ (placeholder) => placeholder.includes(".")
567
+ );
568
+ const isIdentityField = configuredMainField === "id" || configuredMainField === "documentId";
569
+ const displayField = isNestedTemplate && isIdentityField ? "label" : isNestedTemplate ? configuredMainField : compiled.displayField;
455
570
  metadatas[fieldName] = {
456
571
  ...metadata,
457
572
  edit: {
@@ -459,7 +574,7 @@ const relationLabels = ({ strapi }) => {
459
574
  mainField: displayField
460
575
  },
461
576
  list: {
462
- ...isRecord$1(metadata.list) ? metadata.list : {},
577
+ ...isRecord(metadata.list) ? metadata.list : {},
463
578
  mainField: displayField
464
579
  }
465
580
  };
@@ -469,13 +584,13 @@ const relationLabels = ({ strapi }) => {
469
584
  ...configuration,
470
585
  metadatas,
471
586
  settings: {
472
- ...isRecord$1(configuration.settings) ? configuration.settings : {},
587
+ ...isRecord(configuration.settings) ? configuration.settings : {},
473
588
  relationNames
474
589
  }
475
590
  };
476
591
  };
477
592
  const decorateContentManagerConfiguration = async (data, sourceUid) => {
478
- if (!isRecord$1(data)) {
593
+ if (!isRecord(data)) {
479
594
  return data;
480
595
  }
481
596
  const settings2 = await getSettings();
@@ -489,7 +604,7 @@ const relationLabels = ({ strapi }) => {
489
604
  if (next.component) {
490
605
  next.component = await decorateConfiguration(next.component, sourceUid);
491
606
  }
492
- if (isRecord$1(next.components)) {
607
+ if (isRecord(next.components)) {
493
608
  const components = { ...next.components };
494
609
  for (const [uid, configuration] of Object.entries(components)) {
495
610
  components[uid] = await decorateConfiguration(configuration, uid);
@@ -498,6 +613,33 @@ const relationLabels = ({ strapi }) => {
498
613
  }
499
614
  return next;
500
615
  };
616
+ const sanitizeConfigurationUpdate = (configuration) => {
617
+ if (!isRecord(configuration) || !isRecord(configuration.metadatas)) {
618
+ return configuration;
619
+ }
620
+ const configurationSettings = isRecord(configuration.settings) ? configuration.settings : {};
621
+ const relationNames = isRecord(configurationSettings.relationNames) ? configurationSettings.relationNames : {};
622
+ const { relationNames: _relationNames, ...sanitizedSettings } = configurationSettings;
623
+ const metadatas = { ...configuration.metadatas };
624
+ for (const [fieldName, metadata] of Object.entries(metadatas)) {
625
+ const relationName = relationNames[fieldName];
626
+ if (!isRecord(relationName) || relationName.mainField !== "label" || !isRecord(metadata) || !isRecord(metadata.edit) || metadata.edit.mainField !== "label") {
627
+ continue;
628
+ }
629
+ metadatas[fieldName] = {
630
+ ...metadata,
631
+ edit: {
632
+ ...metadata.edit,
633
+ mainField: "id"
634
+ },
635
+ list: {
636
+ ...isRecord(metadata.list) ? metadata.list : {},
637
+ mainField: "id"
638
+ }
639
+ };
640
+ }
641
+ return { ...configuration, settings: sanitizedSettings, metadatas };
642
+ };
501
643
  const decorateCollectionResults = async (ctx, sourceUid, results) => {
502
644
  const settings2 = await getSettings();
503
645
  if (!isCollectionEnabled(settings2.collections, sourceUid)) {
@@ -505,6 +647,7 @@ const relationLabels = ({ strapi }) => {
505
647
  }
506
648
  const runtimeCache = /* @__PURE__ */ new Map();
507
649
  const sourceSchemas = /* @__PURE__ */ new Map();
650
+ const transformContext = getTransformContext(strapi);
508
651
  const getSchema = (uid) => {
509
652
  if (!sourceSchemas.has(uid)) {
510
653
  sourceSchemas.set(uid, strapi.getModel(uid));
@@ -518,7 +661,7 @@ const relationLabels = ({ strapi }) => {
518
661
  }
519
662
  return;
520
663
  }
521
- if (!isRecord$1(value)) {
664
+ if (!isRecord(value)) {
522
665
  return;
523
666
  }
524
667
  const schema = getSchema(uid);
@@ -528,7 +671,14 @@ const relationLabels = ({ strapi }) => {
528
671
  if (!runtimeCache.has(cacheKey)) {
529
672
  runtimeCache.set(
530
673
  cacheKey,
531
- await getRuntime(strapi, settings2, uid, fieldName, ctx.state.userAbility)
674
+ await getRuntime(
675
+ strapi,
676
+ settings2,
677
+ uid,
678
+ fieldName,
679
+ ctx.state.userAbility,
680
+ transformContext
681
+ )
532
682
  );
533
683
  }
534
684
  const runtime = runtimeCache.get(cacheKey);
@@ -564,6 +714,7 @@ const relationLabels = ({ strapi }) => {
564
714
  return {
565
715
  decorateCollectionResults,
566
716
  decorateConfiguration: decorateContentManagerConfiguration,
717
+ sanitizeConfigurationUpdate,
567
718
  decorateRelationResults,
568
719
  compileTemplate,
569
720
  parseTemplate,
@@ -574,9 +725,7 @@ const EMPTY_SETTINGS = {
574
725
  collections: [],
575
726
  relations: {}
576
727
  };
577
- const PLUGIN_ID = "strapi-relation-names";
578
728
  const STORE_KEY = "settings";
579
- const isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
580
729
  const normalizeSettings = (value, collections = []) => {
581
730
  if (!isRecord(value) || !isRecord(value.relations)) {
582
731
  return { collections, relations: {} };
@@ -638,4 +787,4 @@ const index = {
638
787
  policies,
639
788
  middlewares
640
789
  };
641
- exports.default = index;
790
+ module.exports = index;