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