@strapi/content-type-builder 5.55.1 → 5.57.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 (152) hide show
  1. package/dist/admin/components/AIChat/lib/transforms/schemas/toCTB.js +117 -13
  2. package/dist/admin/components/AIChat/lib/transforms/schemas/toCTB.js.map +1 -1
  3. package/dist/admin/components/AIChat/lib/transforms/schemas/toCTB.mjs +118 -13
  4. package/dist/admin/components/AIChat/lib/transforms/schemas/toCTB.mjs.map +1 -1
  5. package/dist/admin/components/AIChat/providers/SchemaProvider.js +31 -19
  6. package/dist/admin/components/AIChat/providers/SchemaProvider.js.map +1 -1
  7. package/dist/admin/components/AIChat/providers/SchemaProvider.mjs +31 -19
  8. package/dist/admin/components/AIChat/providers/SchemaProvider.mjs.map +1 -1
  9. package/dist/admin/components/DataManager/DataManagerContext.js.map +1 -1
  10. package/dist/admin/components/DataManager/DataManagerContext.mjs.map +1 -1
  11. package/dist/admin/components/DataManager/DataManagerProvider.js +166 -6
  12. package/dist/admin/components/DataManager/DataManagerProvider.js.map +1 -1
  13. package/dist/admin/components/DataManager/DataManagerProvider.mjs +165 -7
  14. package/dist/admin/components/DataManager/DataManagerProvider.mjs.map +1 -1
  15. package/dist/admin/components/DataManager/RenameMigrationModal.js +285 -0
  16. package/dist/admin/components/DataManager/RenameMigrationModal.js.map +1 -0
  17. package/dist/admin/components/DataManager/RenameMigrationModal.mjs +257 -0
  18. package/dist/admin/components/DataManager/RenameMigrationModal.mjs.map +1 -0
  19. package/dist/admin/components/DataManager/reducer.js +107 -2
  20. package/dist/admin/components/DataManager/reducer.js.map +1 -1
  21. package/dist/admin/components/DataManager/reducer.mjs +106 -2
  22. package/dist/admin/components/DataManager/reducer.mjs.map +1 -1
  23. package/dist/admin/components/DataManager/utils/cleanData.js +8 -1
  24. package/dist/admin/components/DataManager/utils/cleanData.js.map +1 -1
  25. package/dist/admin/components/DataManager/utils/cleanData.mjs +8 -1
  26. package/dist/admin/components/DataManager/utils/cleanData.mjs.map +1 -1
  27. package/dist/admin/components/DataManager/utils/groupRenameChains.js +86 -0
  28. package/dist/admin/components/DataManager/utils/groupRenameChains.js.map +1 -0
  29. package/dist/admin/components/DataManager/utils/groupRenameChains.mjs +84 -0
  30. package/dist/admin/components/DataManager/utils/groupRenameChains.mjs.map +1 -0
  31. package/dist/admin/components/DataManager/utils/isStorageCompatibleRename.js +29 -0
  32. package/dist/admin/components/DataManager/utils/isStorageCompatibleRename.js.map +1 -0
  33. package/dist/admin/components/DataManager/utils/isStorageCompatibleRename.mjs +27 -0
  34. package/dist/admin/components/DataManager/utils/isStorageCompatibleRename.mjs.map +1 -0
  35. package/dist/admin/components/DataManager/utils/resolveAfterEditRenameConsent.js +35 -0
  36. package/dist/admin/components/DataManager/utils/resolveAfterEditRenameConsent.js.map +1 -0
  37. package/dist/admin/components/DataManager/utils/resolveAfterEditRenameConsent.mjs +33 -0
  38. package/dist/admin/components/DataManager/utils/resolveAfterEditRenameConsent.mjs.map +1 -0
  39. package/dist/admin/components/DataManager/utils/splitChainsByInheritedConsent.js +48 -0
  40. package/dist/admin/components/DataManager/utils/splitChainsByInheritedConsent.js.map +1 -0
  41. package/dist/admin/components/DataManager/utils/splitChainsByInheritedConsent.mjs +45 -0
  42. package/dist/admin/components/DataManager/utils/splitChainsByInheritedConsent.mjs.map +1 -0
  43. package/dist/admin/components/FormModal/FormModal.js +143 -9
  44. package/dist/admin/components/FormModal/FormModal.js.map +1 -1
  45. package/dist/admin/components/FormModal/FormModal.mjs +144 -10
  46. package/dist/admin/components/FormModal/FormModal.mjs.map +1 -1
  47. package/dist/admin/components/FormModal/attributes/validation/common.js +6 -5
  48. package/dist/admin/components/FormModal/attributes/validation/common.js.map +1 -1
  49. package/dist/admin/components/FormModal/attributes/validation/common.mjs +1 -1
  50. package/dist/admin/components/FormModal/attributes/validation/common.mjs.map +1 -1
  51. package/dist/admin/components/FormModal/component/createComponentSchema.js +10 -7
  52. package/dist/admin/components/FormModal/component/createComponentSchema.js.map +1 -1
  53. package/dist/admin/components/FormModal/component/createComponentSchema.mjs +1 -1
  54. package/dist/admin/components/FormModal/component/createComponentSchema.mjs.map +1 -1
  55. package/dist/admin/components/FormModal/contentType/createContentTypeSchema.js +24 -21
  56. package/dist/admin/components/FormModal/contentType/createContentTypeSchema.js.map +1 -1
  57. package/dist/admin/components/FormModal/contentType/createContentTypeSchema.mjs +1 -1
  58. package/dist/admin/components/FormModal/contentType/createContentTypeSchema.mjs.map +1 -1
  59. package/dist/admin/components/FormModal/forms/utils/createCollectionName.js +3 -2
  60. package/dist/admin/components/FormModal/forms/utils/createCollectionName.js.map +1 -1
  61. package/dist/admin/components/FormModal/forms/utils/createCollectionName.mjs +1 -1
  62. package/dist/admin/components/FormModal/forms/utils/createCollectionName.mjs.map +1 -1
  63. package/dist/admin/components/FormModal/utils/getRenameStorageChange.js +73 -0
  64. package/dist/admin/components/FormModal/utils/getRenameStorageChange.js.map +1 -0
  65. package/dist/admin/components/FormModal/utils/getRenameStorageChange.mjs +70 -0
  66. package/dist/admin/components/FormModal/utils/getRenameStorageChange.mjs.map +1 -0
  67. package/dist/admin/src/components/AIChat/components/AnimatedBox.d.ts +2 -2
  68. package/dist/admin/src/components/AIChat/components/Input.d.ts +2 -2
  69. package/dist/admin/src/components/AIChat/lib/transforms/schemas/toCTB.d.ts +4 -3
  70. package/dist/admin/src/components/AIChat/lib/types/schema.d.ts +6 -1
  71. package/dist/admin/src/components/AttributeOptions/OptionBoxWrapper.d.ts +2 -2
  72. package/dist/admin/src/components/ComponentRow.d.ts +2 -2
  73. package/dist/admin/src/components/DataManager/DataManagerContext.d.ts +28 -2
  74. package/dist/admin/src/components/DataManager/RenameMigrationModal.d.ts +73 -0
  75. package/dist/admin/src/components/DataManager/reducer.d.ts +17 -2
  76. package/dist/admin/src/components/DataManager/utils/cleanData.d.ts +16 -0
  77. package/dist/admin/src/components/DataManager/utils/groupRenameChains.d.ts +31 -0
  78. package/dist/admin/src/components/DataManager/utils/isStorageCompatibleRename.d.ts +10 -0
  79. package/dist/admin/src/components/DataManager/utils/resolveAfterEditRenameConsent.d.ts +24 -0
  80. package/dist/admin/src/components/DataManager/utils/splitChainsByInheritedConsent.d.ts +26 -0
  81. package/dist/admin/src/components/FormModal/utils/getRenameStorageChange.d.ts +40 -0
  82. package/dist/admin/src/components/List.d.ts +2 -2
  83. package/dist/admin/src/components/Relation/RelationNaturePicker/Components.d.ts +4 -4
  84. package/dist/admin/src/types.d.ts +23 -0
  85. package/dist/admin/translations/en.json.js +12 -0
  86. package/dist/admin/translations/en.json.js.map +1 -1
  87. package/dist/admin/translations/en.json.mjs +12 -0
  88. package/dist/admin/translations/en.json.mjs.map +1 -1
  89. package/dist/server/config.js +24 -3
  90. package/dist/server/config.js.map +1 -1
  91. package/dist/server/config.mjs +21 -3
  92. package/dist/server/config.mjs.map +1 -1
  93. package/dist/server/controllers/schema.js +2 -2
  94. package/dist/server/controllers/schema.js.map +1 -1
  95. package/dist/server/controllers/schema.mjs +1 -1
  96. package/dist/server/controllers/schema.mjs.map +1 -1
  97. package/dist/server/controllers/validation/content-type.js +9 -11
  98. package/dist/server/controllers/validation/content-type.js.map +1 -1
  99. package/dist/server/controllers/validation/content-type.mjs +7 -9
  100. package/dist/server/controllers/validation/content-type.mjs.map +1 -1
  101. package/dist/server/controllers/validation/model-schema.js +2 -3
  102. package/dist/server/controllers/validation/model-schema.js.map +1 -1
  103. package/dist/server/controllers/validation/model-schema.mjs +1 -2
  104. package/dist/server/controllers/validation/model-schema.mjs.map +1 -1
  105. package/dist/server/controllers/validation/relations.js +1 -2
  106. package/dist/server/controllers/validation/relations.js.map +1 -1
  107. package/dist/server/controllers/validation/relations.mjs +1 -2
  108. package/dist/server/controllers/validation/relations.mjs.map +1 -1
  109. package/dist/server/controllers/validation/schema.js +42 -23
  110. package/dist/server/controllers/validation/schema.js.map +1 -1
  111. package/dist/server/controllers/validation/schema.mjs +43 -24
  112. package/dist/server/controllers/validation/schema.mjs.map +1 -1
  113. package/dist/server/index.js +1 -1
  114. package/dist/server/index.js.map +1 -1
  115. package/dist/server/services/content-types.js +1 -2
  116. package/dist/server/services/content-types.js.map +1 -1
  117. package/dist/server/services/content-types.mjs +2 -3
  118. package/dist/server/services/content-types.mjs.map +1 -1
  119. package/dist/server/services/migration-builder/index.js +580 -0
  120. package/dist/server/services/migration-builder/index.js.map +1 -0
  121. package/dist/server/services/migration-builder/index.mjs +573 -0
  122. package/dist/server/services/migration-builder/index.mjs.map +1 -0
  123. package/dist/server/services/schema-builder/content-type-builder.js +31 -1
  124. package/dist/server/services/schema-builder/content-type-builder.js.map +1 -1
  125. package/dist/server/services/schema-builder/content-type-builder.mjs +31 -1
  126. package/dist/server/services/schema-builder/content-type-builder.mjs.map +1 -1
  127. package/dist/server/services/schema-mutation.js +30 -2
  128. package/dist/server/services/schema-mutation.js.map +1 -1
  129. package/dist/server/services/schema-mutation.mjs +10 -2
  130. package/dist/server/services/schema-mutation.mjs.map +1 -1
  131. package/dist/server/services/schema.js +259 -15
  132. package/dist/server/services/schema.js.map +1 -1
  133. package/dist/server/services/schema.mjs +252 -9
  134. package/dist/server/services/schema.mjs.map +1 -1
  135. package/dist/server/src/config.d.ts +23 -2
  136. package/dist/server/src/config.d.ts.map +1 -1
  137. package/dist/server/src/controllers/validation/content-type.d.ts +0 -1
  138. package/dist/server/src/controllers/validation/content-type.d.ts.map +1 -1
  139. package/dist/server/src/controllers/validation/relations.d.ts.map +1 -1
  140. package/dist/server/src/controllers/validation/schema.d.ts +28 -0
  141. package/dist/server/src/controllers/validation/schema.d.ts.map +1 -1
  142. package/dist/server/src/index.d.ts +10 -2
  143. package/dist/server/src/index.d.ts.map +1 -1
  144. package/dist/server/src/services/content-types.d.ts.map +1 -1
  145. package/dist/server/src/services/migration-builder/index.d.ts +83 -0
  146. package/dist/server/src/services/migration-builder/index.d.ts.map +1 -0
  147. package/dist/server/src/services/schema-builder/content-type-builder.d.ts.map +1 -1
  148. package/dist/server/src/services/schema-mutation.d.ts +4 -2
  149. package/dist/server/src/services/schema-mutation.d.ts.map +1 -1
  150. package/dist/server/src/services/schema.d.ts +19 -0
  151. package/dist/server/src/services/schema.d.ts.map +1 -1
  152. package/package.json +11 -10
@@ -0,0 +1,580 @@
1
+ 'use strict';
2
+
3
+ var path = require('node:path');
4
+ var utils = require('@strapi/utils');
5
+
6
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
7
+
8
+ var path__default = /*#__PURE__*/_interopDefault(path);
9
+
10
+ /**
11
+ * Whether a relation definition has a counterpart on its target. The live
12
+ * schema carries `inversedBy` / `mappedBy`; the CTB payload carries
13
+ * `targetAttribute`.
14
+ */ const isBidirectionalRelation = (attribute)=>Boolean(attribute.inversedBy || attribute.mappedBy || attribute.targetAttribute);
15
+ /**
16
+ * Whether the storage of `oldAttribute` can host `newAttribute` after a plain
17
+ * rename of its physical artifact. Anything else must go through the legacy
18
+ * drop-and-recreate path, so the hop is reported as `type-changed`.
19
+ *
20
+ * `resolveType` maps a definition to its storage type. The builder passes one
21
+ * that resolves custom fields (sent as `type: 'customField'` by the admin, but
22
+ * already converted to their underlying type in the live schema).
23
+ */ const isCompatibleRename = (oldAttribute, newAttribute, resolveType = (attribute)=>attribute.type)=>{
24
+ const oldType = oldAttribute ? resolveType(oldAttribute) : undefined;
25
+ const newType = newAttribute ? resolveType(newAttribute) : undefined;
26
+ // Without both definitions there is nothing to compare; the metadata-based
27
+ // classification is the only guard we have.
28
+ if (!oldAttribute || !newAttribute || !oldType || !newType) {
29
+ return true;
30
+ }
31
+ if (oldType !== newType) {
32
+ return false;
33
+ }
34
+ // A different custom field on the same underlying type is still a different
35
+ // field.
36
+ if ((oldAttribute.customField || newAttribute.customField) && oldAttribute.customField !== newAttribute.customField) {
37
+ return false;
38
+ }
39
+ switch(newType){
40
+ case 'relation':
41
+ // `manyWay` and a bidirectional `oneToMany` share `relation: 'oneToMany'`,
42
+ // but switching between them moves join-table ownership.
43
+ return oldAttribute.relation === newAttribute.relation && oldAttribute.target === newAttribute.target && isBidirectionalRelation(oldAttribute) === isBidirectionalRelation(newAttribute);
44
+ case 'component':
45
+ return oldAttribute.component === newAttribute.component && Boolean(oldAttribute.repeatable) === Boolean(newAttribute.repeatable);
46
+ default:
47
+ return true;
48
+ }
49
+ };
50
+ const MIGRATION_NAME = 'rename-fields';
51
+ const createMigrationBuilder = ({ strapi })=>{
52
+ const { db } = strapi;
53
+ const migrationFileBuilder = db.migrations.createFileBuilder();
54
+ const unsupported = [];
55
+ // Physical-name rules shared with the metadata loader, so the *new* side of a
56
+ // rename is derived exactly as `db.metadata` will derive it after the reload.
57
+ const { naming } = db.metadata;
58
+ /**
59
+ * The app's *source* migrations dir. Generated files always land here (see
60
+ * `writeFiles`), and it is also the dir discovery reads from when
61
+ * `useTypescriptMigrations` is off or no tsconfig `outDir` resolves.
62
+ */ const sourceMigrationsDir = ()=>path__default.default.join(strapi.dirs.app.root, 'database', 'migrations');
63
+ let format;
64
+ /**
65
+ * Mirror the database's discovery decision (`Strapi.ts`): it reads from
66
+ * `<outDir>/database/migrations` only when `useTypescriptMigrations` is on
67
+ * *and* a tsconfig `outDir` resolves. In that case the source file must be
68
+ * `.ts` so `tsc` carries it into `dist`. Otherwise discovery reads the source
69
+ * dir and only loads `.js`, so a `.ts` file would never run.
70
+ *
71
+ * Memoised so the fallback warning is logged once per builder (both `build`
72
+ * and `writeFiles` call this).
73
+ */ const getFormat = ()=>{
74
+ if (format) {
75
+ return format;
76
+ }
77
+ const useTSM = strapi.config.get('database.settings.useTypescriptMigrations') === true;
78
+ const discoveryDir = db.config.settings.migrations?.dir;
79
+ const discoversFromDist = typeof discoveryDir === 'string' && path__default.default.resolve(discoveryDir) !== path__default.default.resolve(sourceMigrationsDir());
80
+ if (useTSM && !discoversFromDist) {
81
+ strapi.log.warn('database.settings.useTypescriptMigrations is enabled but no TypeScript outDir was resolved for this app; ' + 'writing the rename migration as JavaScript so it is discovered from database/migrations.');
82
+ }
83
+ format = useTSM && discoversFromDist ? 'typescript' : 'javascript';
84
+ return format;
85
+ };
86
+ // Artifacts "in flight" for the current save, i.e. produced by an earlier
87
+ // rename hop in this same batch (e.g. the intermediate `tmp` field a user
88
+ // routes a swap through). Keyed per uid by the *logical* field name the hop
89
+ // produced; the value records how that field resolves to a physical artifact
90
+ // and its current `from` identifier, so a continuation hop replays from the
91
+ // right place instead of being treated as a brand-new, unknown attribute.
92
+ const inFlight = new Map();
93
+ // CTB-level definition of each in-flight field (per uid, by logical name), so
94
+ // a continuation hop can still be checked for a storage change against the
95
+ // definition the chain started from.
96
+ const inFlightDefinitions = new Map();
97
+ // Logical names vacated by an earlier accepted hop of this batch (per uid). A
98
+ // refused hop never vacates its source: the artifact stays live until schema
99
+ // sync drops it. A hop may only target a name that is free: not live in the
100
+ // pre-reload schema (unless an earlier accepted hop vacated it) and not
101
+ // produced by an earlier hop still in flight. At migration time the schema-sync drop of a still-live field has
102
+ // not happened yet, so renaming onto it would collide and the runtime guard
103
+ // would silently skip the hop. Refusing it here surfaces the problem (e.g. a
104
+ // truncated chain sent by a client) instead of losing data.
105
+ const vacated = new Map();
106
+ const isTargetOccupied = (uid, newName)=>{
107
+ if (inFlight.get(uid)?.has(newName)) {
108
+ return true;
109
+ }
110
+ return Boolean(schemaAttributeOf(uid, newName)) && !(vacated.get(uid)?.has(newName) ?? false);
111
+ };
112
+ const trackVacated = (uid, oldName, newName)=>{
113
+ let names = vacated.get(uid);
114
+ if (!names) {
115
+ names = new Set();
116
+ vacated.set(uid, names);
117
+ }
118
+ names.add(oldName);
119
+ // The target is occupied again by the in-flight field.
120
+ names.delete(newName);
121
+ };
122
+ const markInFlight = (uid, name, resolved)=>{
123
+ let entries = inFlight.get(uid);
124
+ if (!entries) {
125
+ entries = new Map();
126
+ inFlight.set(uid, entries);
127
+ }
128
+ entries.set(name, resolved);
129
+ };
130
+ // Per uid, logical origin name -> current name, folded over every received hop.
131
+ const origins = new Map();
132
+ const trackOrigin = (uid, oldName, newName)=>{
133
+ let byOrigin = origins.get(uid);
134
+ if (!byOrigin) {
135
+ byOrigin = new Map();
136
+ origins.set(uid, byOrigin);
137
+ }
138
+ for (const [origin, current] of byOrigin){
139
+ if (current === oldName) {
140
+ byOrigin.set(origin, newName);
141
+ return;
142
+ }
143
+ }
144
+ // A name that is already an origin but no longer current was re-occupied by
145
+ // something this save did not rename; do not overwrite its logical field.
146
+ if (!byOrigin.has(oldName)) {
147
+ byOrigin.set(oldName, newName);
148
+ }
149
+ };
150
+ const isMorphRelation = (attribute)=>typeof attribute.relation === 'string' && attribute.relation.startsWith('morph');
151
+ const schemaModelOf = (uid)=>{
152
+ const contentTypes = strapi.contentTypes;
153
+ const components = strapi.components;
154
+ return contentTypes?.[uid] ?? components?.[uid];
155
+ };
156
+ const schemaAttributeOf = (uid, name)=>schemaModelOf(uid)?.attributes?.[name];
157
+ /**
158
+ * The type an attribute is stored as. Custom fields are sent by the admin as
159
+ * `type: 'customField'`, while the live schema already carries their
160
+ * underlying type, so both sides are resolved through the registry.
161
+ */ const storageTypeOf = (attribute)=>{
162
+ if (attribute.type !== 'customField' && typeof attribute.customField !== 'string') {
163
+ return attribute.type;
164
+ }
165
+ try {
166
+ const customField = strapi.get('custom-fields').get(attribute.customField);
167
+ return customField?.type ?? attribute.type;
168
+ } catch {
169
+ // Unknown custom field (e.g. its plugin was removed).
170
+ return attribute.type;
171
+ }
172
+ };
173
+ const schemaTypeOf = (uid, name)=>schemaAttributeOf(uid, name)?.type;
174
+ // Resolves the shared upload morph table (e.g. `files_related_morphs`) and its
175
+ // `related_type` column from the file model's `related` morph attribute. This
176
+ // is the single table every media field across all types writes to, keyed by
177
+ // (`field` = attribute name, `related_type` = owning uid).
178
+ const resolveUploadMorphTable = ()=>{
179
+ if (!db.metadata.has('plugin::upload.file')) {
180
+ return undefined;
181
+ }
182
+ const fileMeta = db.metadata.get('plugin::upload.file');
183
+ const related = fileMeta.attributes?.related;
184
+ const joinTable = related?.joinTable;
185
+ const morphColumn = joinTable?.morphColumn ?? related?.morphColumn;
186
+ if (!joinTable?.name || !morphColumn?.typeColumn?.name) {
187
+ return undefined;
188
+ }
189
+ return {
190
+ table: joinTable.name,
191
+ fieldColumn: db.metadata.identifiers.FIELD_COLUMN,
192
+ typeColumn: morphColumn.typeColumn.name
193
+ };
194
+ };
195
+ /**
196
+ * Classifies the *first* hop of a rename by inspecting the live metadata, which
197
+ * still reflects the pre-rename schema. Continuation hops never reach here —
198
+ * they inherit their classification from the in-flight map.
199
+ */ const classifyMedia = (uid, oldName, joinTable, upload)=>{
200
+ // Media: the attribute name is a value in the shared `files_related_morphs`
201
+ // `field` column, scoped by `related_type` (the owning uid). Detect it by
202
+ // schema type OR by the join table being the shared upload table — the
203
+ // latter guard is critical so a media field can never fall through to the
204
+ // unscoped component branch and corrupt other types' media on the shared
205
+ // table.
206
+ const isMedia = schemaTypeOf(uid, oldName) === 'media' || !!upload && joinTable?.name === upload.table;
207
+ if (!isMedia) {
208
+ return undefined;
209
+ }
210
+ if (!upload) {
211
+ return {
212
+ kind: 'unsupported',
213
+ reason: 'unsupported-type'
214
+ };
215
+ }
216
+ return {
217
+ kind: 'media',
218
+ table: upload.table,
219
+ fieldColumn: upload.fieldColumn,
220
+ typeColumn: upload.typeColumn,
221
+ typeValue: uid,
222
+ from: oldName
223
+ };
224
+ };
225
+ const classifyComponent = (oldName, joinTable)=>{
226
+ const on = joinTable?.on;
227
+ if (!joinTable?.name || !on || typeof on !== 'object') {
228
+ return undefined;
229
+ }
230
+ const fieldColumn = Object.keys(on).find((key)=>on[key] === oldName);
231
+ if (!fieldColumn) {
232
+ return undefined;
233
+ }
234
+ return {
235
+ kind: 'component',
236
+ table: joinTable.name,
237
+ fieldColumn,
238
+ from: oldName
239
+ };
240
+ };
241
+ const classifyRelation = (meta, attribute, joinTable)=>{
242
+ // Polymorphic morph relations need shared-table handling and are not
243
+ // creatable through the CTB UI, so they are left unsupported.
244
+ if (isMorphRelation(attribute) || attribute.morphColumn) {
245
+ return {
246
+ kind: 'unsupported',
247
+ reason: 'unsupported-type'
248
+ };
249
+ }
250
+ // Join column on the owner's own table (`<field>_id`), e.g. useJoinTable:false.
251
+ // The inverse side of a bidirectional join-column relation also carries a
252
+ // `joinColumn` (pointing at `id`), but owns no column.
253
+ if (attribute.joinColumn?.name && !joinTable) {
254
+ if (attribute.mappedBy) {
255
+ return {
256
+ kind: 'skip'
257
+ };
258
+ }
259
+ return {
260
+ kind: 'joinColumn',
261
+ table: meta.tableName,
262
+ from: attribute.joinColumn.name
263
+ };
264
+ }
265
+ if (joinTable?.name) {
266
+ // The inverse side of a bidirectional relation: the join table is named
267
+ // from the owning attribute, so renaming this side touches no artifact.
268
+ if (attribute.mappedBy) {
269
+ return {
270
+ kind: 'skip'
271
+ };
272
+ }
273
+ return {
274
+ kind: 'joinTable',
275
+ from: joinTable.name
276
+ };
277
+ }
278
+ return {
279
+ kind: 'unsupported',
280
+ reason: 'unsupported-type'
281
+ };
282
+ };
283
+ /**
284
+ * Whether the schema lets users rename this attribute at all. System
285
+ * attributes (`documentId`, `createdBy`, `locale`, `publishedAt`, review
286
+ * workflow stages…) are also in the metadata and would otherwise be renamed
287
+ * away, only for sync to re-add them empty.
288
+ */ const isRenamableAttribute = (uid, name)=>{
289
+ const schemaAttribute = schemaAttributeOf(uid, name);
290
+ // Same rule as payload validation applies to a hop's old name: `status` is
291
+ // only reserved with draft and publish, and a legacy field may carry it.
292
+ if (schemaAttribute?.configurable === false || utils.contentTypes.isReservedAttributeName(name, {
293
+ draftAndPublish: false
294
+ })) {
295
+ return false;
296
+ }
297
+ const model = schemaModelOf(uid);
298
+ return !utils.contentTypes.getNonVisibleAttributes(model).includes(name);
299
+ };
300
+ const classify = (uid, oldName)=>{
301
+ if (!schemaAttributeOf(uid, oldName)) {
302
+ return {
303
+ kind: 'unsupported',
304
+ reason: 'attribute-not-found'
305
+ };
306
+ }
307
+ if (!isRenamableAttribute(uid, oldName)) {
308
+ return {
309
+ kind: 'unsupported',
310
+ reason: 'unsupported-type'
311
+ };
312
+ }
313
+ const meta = db.metadata.get(uid);
314
+ const attribute = meta.attributes?.[oldName];
315
+ if (!attribute) {
316
+ return {
317
+ kind: 'unsupported',
318
+ reason: 'attribute-not-found'
319
+ };
320
+ }
321
+ const joinTable = attribute.joinTable;
322
+ const upload = resolveUploadMorphTable();
323
+ const media = classifyMedia(uid, oldName, joinTable, upload);
324
+ if (media) {
325
+ return media;
326
+ }
327
+ const component = classifyComponent(oldName, joinTable);
328
+ if (component) {
329
+ return component;
330
+ }
331
+ if (attribute.type === 'relation') {
332
+ return classifyRelation(meta, attribute, joinTable);
333
+ }
334
+ // Plain scalar column on the model's own table.
335
+ if ('columnName' in attribute && attribute.columnName) {
336
+ return {
337
+ kind: 'scalarColumn',
338
+ table: meta.tableName,
339
+ from: attribute.columnName
340
+ };
341
+ }
342
+ return {
343
+ kind: 'unsupported',
344
+ reason: 'unsupported-type'
345
+ };
346
+ };
347
+ /**
348
+ * Computes the destination identifier for a resolved artifact given the new
349
+ * field name, and the next in-flight state (with `from` advanced to `to`).
350
+ */ const advance = (uid, resolved, oldName, newName)=>{
351
+ const meta = db.metadata.get(uid);
352
+ const comment = `${uid}: rename field "${oldName}" -> "${newName}"`;
353
+ switch(resolved.kind){
354
+ case 'scalarColumn':
355
+ case 'joinColumn':
356
+ {
357
+ const to = resolved.kind === 'joinColumn' ? naming.joinColumnName(newName) : naming.columnName(newName);
358
+ if (resolved.from !== to) {
359
+ migrationFileBuilder.renameColumn({
360
+ table: resolved.table,
361
+ from: resolved.from,
362
+ to,
363
+ comment
364
+ });
365
+ }
366
+ return {
367
+ ...resolved,
368
+ from: to
369
+ };
370
+ }
371
+ case 'joinTable':
372
+ {
373
+ const to = naming.joinTableName(meta.tableName, newName);
374
+ if (resolved.from !== to) {
375
+ migrationFileBuilder.renameTable({
376
+ from: resolved.from,
377
+ to,
378
+ comment
379
+ });
380
+ }
381
+ return {
382
+ ...resolved,
383
+ from: to
384
+ };
385
+ }
386
+ case 'component':
387
+ {
388
+ const to = newName;
389
+ if (resolved.from !== to) {
390
+ migrationFileBuilder.updateRows({
391
+ table: resolved.table,
392
+ guardColumn: resolved.fieldColumn,
393
+ where: {
394
+ [resolved.fieldColumn]: resolved.from
395
+ },
396
+ set: {
397
+ [resolved.fieldColumn]: to
398
+ },
399
+ comment
400
+ });
401
+ }
402
+ return {
403
+ ...resolved,
404
+ from: to
405
+ };
406
+ }
407
+ case 'media':
408
+ {
409
+ const to = newName;
410
+ if (resolved.from !== to) {
411
+ migrationFileBuilder.updateRows({
412
+ table: resolved.table,
413
+ guardColumn: resolved.fieldColumn,
414
+ where: {
415
+ [resolved.fieldColumn]: resolved.from,
416
+ [resolved.typeColumn]: resolved.typeValue
417
+ },
418
+ set: {
419
+ [resolved.fieldColumn]: to
420
+ },
421
+ comment
422
+ });
423
+ }
424
+ return {
425
+ ...resolved,
426
+ from: to
427
+ };
428
+ }
429
+ default:
430
+ return resolved;
431
+ }
432
+ };
433
+ /**
434
+ * Records one rename hop exactly in the order the user performed it. Because the
435
+ * Content-Type Builder forbids two fields sharing a name at any instant, the
436
+ * recorded path is inherently collision-free: replaying each hop verbatim never
437
+ * targets an occupied artifact, so no synthetic temporary name is ever needed
438
+ * (a "swap" is already expressed by the user's own intermediate-name hop).
439
+ */ const addRename = (uid, { oldName, newName, newAttribute })=>{
440
+ // `metadata.get` throws when the model is unknown, so guard with `has` first.
441
+ if (!db.metadata.has(uid)) {
442
+ unsupported.push({
443
+ uid,
444
+ oldName,
445
+ newName,
446
+ reason: 'model-not-found'
447
+ });
448
+ return;
449
+ }
450
+ trackOrigin(uid, oldName, newName);
451
+ if (isTargetOccupied(uid, newName)) {
452
+ unsupported.push({
453
+ uid,
454
+ oldName,
455
+ newName,
456
+ reason: 'target-occupied'
457
+ });
458
+ // Keep the chain consistent so a later continuation hop stays silent. The
459
+ // source is not vacated: its artifact was not moved.
460
+ markInFlight(uid, newName, {
461
+ kind: 'unsupported',
462
+ reason: 'target-occupied'
463
+ });
464
+ return;
465
+ }
466
+ const entries = inFlight.get(uid);
467
+ const definitions = inFlightDefinitions.get(uid);
468
+ const oldAttribute = definitions?.get(oldName) ?? schemaAttributeOf(uid, oldName);
469
+ let resolved = entries?.get(oldName) ?? classify(uid, oldName);
470
+ // A rename that also changes the attribute's storage cannot be expressed as a
471
+ // rename of the physical artifact: leave it to the drop-and-recreate path.
472
+ if (resolved.kind !== 'unsupported' && !isCompatibleRename(oldAttribute, newAttribute, storageTypeOf)) {
473
+ resolved = {
474
+ kind: 'unsupported',
475
+ reason: 'type-changed'
476
+ };
477
+ }
478
+ if (newAttribute ?? oldAttribute) {
479
+ let byName = inFlightDefinitions.get(uid);
480
+ if (!byName) {
481
+ byName = new Map();
482
+ inFlightDefinitions.set(uid, byName);
483
+ }
484
+ byName.set(newName, newAttribute ?? oldAttribute);
485
+ }
486
+ if (resolved.kind === 'unsupported') {
487
+ unsupported.push({
488
+ uid,
489
+ oldName,
490
+ newName,
491
+ reason: resolved.reason
492
+ });
493
+ // Keep the chain consistent so a later continuation hop stays silent. The
494
+ // source keeps its in-flight entry and is not vacated: its physical
495
+ // artifact stays where it is until schema sync drops it.
496
+ markInFlight(uid, newName, resolved);
497
+ return;
498
+ }
499
+ // Accepted from here on: the source name is free and the field moves.
500
+ entries?.delete(oldName);
501
+ definitions?.delete(oldName);
502
+ trackVacated(uid, oldName, newName);
503
+ if (resolved.kind === 'skip') {
504
+ markInFlight(uid, newName, resolved);
505
+ return;
506
+ }
507
+ const next = advance(uid, resolved, oldName, newName);
508
+ markInFlight(uid, newName, next);
509
+ };
510
+ return {
511
+ addRenameAttribute (uid, names) {
512
+ addRename(uid, names);
513
+ },
514
+ /**
515
+ * The composed `origin -> final` name of every schema attribute that
516
+ * received hops in this save, per uid. Identity entries (`a -> b -> a`) and
517
+ * origins that are not schema attributes are left out.
518
+ */ attributeRenamesMapping () {
519
+ const mapping = {};
520
+ for (const [uid, byOrigin] of origins){
521
+ for (const [origin, final] of byOrigin){
522
+ var _mapping, _uid;
523
+ if (origin === final || !schemaAttributeOf(uid, origin)) {
524
+ continue;
525
+ }
526
+ (_mapping = mapping)[_uid = uid] ?? (_mapping[_uid] = {});
527
+ mapping[uid][origin] = final;
528
+ }
529
+ }
530
+ return mapping;
531
+ },
532
+ /**
533
+ * Adds the logical op that carries this save's renames to the stores keyed
534
+ * by attribute name (admin field permissions, via handlers registered on
535
+ * the database). Call it after every hop so it is the last operation.
536
+ */ addAttributeRenames (renames) {
537
+ if (Object.keys(renames).length === 0) {
538
+ return;
539
+ }
540
+ migrationFileBuilder.attributeRenames({
541
+ renames,
542
+ comment: "Update stores keyed by attribute name (admin field permissions) for this save's renames"
543
+ });
544
+ },
545
+ hasChanges () {
546
+ return migrationFileBuilder.hasChanges();
547
+ },
548
+ getUnsupported () {
549
+ return [
550
+ ...unsupported
551
+ ];
552
+ },
553
+ getOperations () {
554
+ return migrationFileBuilder.getOperations();
555
+ },
556
+ build () {
557
+ return migrationFileBuilder.build({
558
+ name: MIGRATION_NAME,
559
+ format: getFormat()
560
+ });
561
+ },
562
+ async writeFiles () {
563
+ // Always write to the app's *source* migrations dir, never the database's
564
+ // configured dir: when `useTypescriptMigrations` is enabled that points at
565
+ // build output (e.g. `dist/database/migrations`), which is gitignored and
566
+ // wiped on the next build, so the generated migration would not be a
567
+ // portable record.
568
+ const dir = sourceMigrationsDir();
569
+ return migrationFileBuilder.writeFiles({
570
+ name: MIGRATION_NAME,
571
+ format: getFormat(),
572
+ dir
573
+ });
574
+ }
575
+ };
576
+ };
577
+
578
+ exports.createMigrationBuilder = createMigrationBuilder;
579
+ exports.isCompatibleRename = isCompatibleRename;
580
+ //# sourceMappingURL=index.js.map