@strapi/content-type-builder 5.54.0 → 5.55.1

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 (208) hide show
  1. package/dist/admin/components/AIChat/hooks/useAIFetch.js +6 -2
  2. package/dist/admin/components/AIChat/hooks/useAIFetch.js.map +1 -1
  3. package/dist/admin/components/AIChat/hooks/useAIFetch.mjs +7 -3
  4. package/dist/admin/components/AIChat/hooks/useAIFetch.mjs.map +1 -1
  5. package/dist/admin/components/AIChat/hooks/usePrefetchAIToken.js +20 -0
  6. package/dist/admin/components/AIChat/hooks/usePrefetchAIToken.js.map +1 -0
  7. package/dist/admin/components/AIChat/hooks/usePrefetchAIToken.mjs +18 -0
  8. package/dist/admin/components/AIChat/hooks/usePrefetchAIToken.mjs.map +1 -0
  9. package/dist/admin/components/ContentTypeBuilderNav/ContentTypeBuilderNav.js +83 -59
  10. package/dist/admin/components/ContentTypeBuilderNav/ContentTypeBuilderNav.js.map +1 -1
  11. package/dist/admin/components/ContentTypeBuilderNav/ContentTypeBuilderNav.mjs +86 -62
  12. package/dist/admin/components/ContentTypeBuilderNav/ContentTypeBuilderNav.mjs.map +1 -1
  13. package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/DeleteFolderDialog.js +112 -0
  14. package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/DeleteFolderDialog.js.map +1 -0
  15. package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/DeleteFolderDialog.mjs +110 -0
  16. package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/DeleteFolderDialog.mjs.map +1 -0
  17. package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/FolderNameField.js +159 -0
  18. package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/FolderNameField.js.map +1 -0
  19. package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/FolderNameField.mjs +157 -0
  20. package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/FolderNameField.mjs.map +1 -0
  21. package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/FolderNavSection.js +311 -0
  22. package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/FolderNavSection.js.map +1 -0
  23. package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/FolderNavSection.mjs +309 -0
  24. package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/FolderNavSection.mjs.map +1 -0
  25. package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/SortableTreeRow.js +379 -0
  26. package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/SortableTreeRow.js.map +1 -0
  27. package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/SortableTreeRow.mjs +376 -0
  28. package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/SortableTreeRow.mjs.map +1 -0
  29. package/dist/admin/components/ContentTypeBuilderNav/hooks/useContentTypeBuilderMenu.js +172 -0
  30. package/dist/admin/components/ContentTypeBuilderNav/hooks/useContentTypeBuilderMenu.js.map +1 -0
  31. package/dist/admin/components/ContentTypeBuilderNav/hooks/useContentTypeBuilderMenu.mjs +166 -0
  32. package/dist/admin/components/ContentTypeBuilderNav/hooks/useContentTypeBuilderMenu.mjs.map +1 -0
  33. package/dist/admin/components/ContentTypeBuilderNav/hooks/useFolderActions.js +62 -0
  34. package/dist/admin/components/ContentTypeBuilderNav/hooks/useFolderActions.js.map +1 -0
  35. package/dist/admin/components/ContentTypeBuilderNav/hooks/useFolderActions.mjs +60 -0
  36. package/dist/admin/components/ContentTypeBuilderNav/hooks/useFolderActions.mjs.map +1 -0
  37. package/dist/admin/components/ContentTypeBuilderNav/hooks/useSortableTree.js +145 -0
  38. package/dist/admin/components/ContentTypeBuilderNav/hooks/useSortableTree.js.map +1 -0
  39. package/dist/admin/components/ContentTypeBuilderNav/hooks/useSortableTree.mjs +143 -0
  40. package/dist/admin/components/ContentTypeBuilderNav/hooks/useSortableTree.mjs.map +1 -0
  41. package/dist/admin/components/ContentTypeBuilderNav/lib/buildFolderTree.js +176 -0
  42. package/dist/admin/components/ContentTypeBuilderNav/lib/buildFolderTree.js.map +1 -0
  43. package/dist/admin/components/ContentTypeBuilderNav/lib/buildFolderTree.mjs +169 -0
  44. package/dist/admin/components/ContentTypeBuilderNav/lib/buildFolderTree.mjs.map +1 -0
  45. package/dist/admin/components/ContentTypeBuilderNav/lib/dropProjection.js +277 -0
  46. package/dist/admin/components/ContentTypeBuilderNav/lib/dropProjection.js.map +1 -0
  47. package/dist/admin/components/ContentTypeBuilderNav/lib/dropProjection.mjs +273 -0
  48. package/dist/admin/components/ContentTypeBuilderNav/lib/dropProjection.mjs.map +1 -0
  49. package/dist/admin/components/ContentTypeBuilderNav/lib/flatModel.js +62 -0
  50. package/dist/admin/components/ContentTypeBuilderNav/lib/flatModel.js.map +1 -0
  51. package/dist/admin/components/ContentTypeBuilderNav/lib/flatModel.mjs +58 -0
  52. package/dist/admin/components/ContentTypeBuilderNav/lib/flatModel.mjs.map +1 -0
  53. package/dist/admin/components/DataManager/DataManagerContext.js.map +1 -1
  54. package/dist/admin/components/DataManager/DataManagerContext.mjs.map +1 -1
  55. package/dist/admin/components/DataManager/DataManagerProvider.js +70 -8
  56. package/dist/admin/components/DataManager/DataManagerProvider.js.map +1 -1
  57. package/dist/admin/components/DataManager/DataManagerProvider.mjs +70 -8
  58. package/dist/admin/components/DataManager/DataManagerProvider.mjs.map +1 -1
  59. package/dist/admin/components/DataManager/reducer.js +411 -33
  60. package/dist/admin/components/DataManager/reducer.js.map +1 -1
  61. package/dist/admin/components/DataManager/reducer.mjs +411 -33
  62. package/dist/admin/components/DataManager/reducer.mjs.map +1 -1
  63. package/dist/admin/components/DataManager/utils/cleanData.js +11 -2
  64. package/dist/admin/components/DataManager/utils/cleanData.js.map +1 -1
  65. package/dist/admin/components/DataManager/utils/cleanData.mjs +11 -2
  66. package/dist/admin/components/DataManager/utils/cleanData.mjs.map +1 -1
  67. package/dist/admin/components/DataManager/utils/contentStructure.js +150 -0
  68. package/dist/admin/components/DataManager/utils/contentStructure.js.map +1 -0
  69. package/dist/admin/components/DataManager/utils/contentStructure.mjs +133 -0
  70. package/dist/admin/components/DataManager/utils/contentStructure.mjs.map +1 -0
  71. package/dist/admin/components/FolderSelect.js +200 -0
  72. package/dist/admin/components/FolderSelect.js.map +1 -0
  73. package/dist/admin/components/FolderSelect.mjs +198 -0
  74. package/dist/admin/components/FolderSelect.mjs.map +1 -0
  75. package/dist/admin/components/FormModal/FormModal.js +23 -2
  76. package/dist/admin/components/FormModal/FormModal.js.map +1 -1
  77. package/dist/admin/components/FormModal/FormModal.mjs +23 -2
  78. package/dist/admin/components/FormModal/FormModal.mjs.map +1 -1
  79. package/dist/admin/components/FormModal/contentType/contentTypeForm.js +15 -18
  80. package/dist/admin/components/FormModal/contentType/contentTypeForm.js.map +1 -1
  81. package/dist/admin/components/FormModal/contentType/contentTypeForm.mjs +15 -18
  82. package/dist/admin/components/FormModal/contentType/contentTypeForm.mjs.map +1 -1
  83. package/dist/admin/components/FormModal/reducer.js +6 -0
  84. package/dist/admin/components/FormModal/reducer.js.map +1 -1
  85. package/dist/admin/components/FormModal/reducer.mjs +6 -0
  86. package/dist/admin/components/FormModal/reducer.mjs.map +1 -1
  87. package/dist/admin/pages/App/index.js +2 -5
  88. package/dist/admin/pages/App/index.js.map +1 -1
  89. package/dist/admin/pages/App/index.mjs +2 -5
  90. package/dist/admin/pages/App/index.mjs.map +1 -1
  91. package/dist/admin/src/components/AIChat/hooks/usePrefetchAIToken.d.ts +1 -0
  92. package/dist/admin/src/components/ContentTypeBuilderNav/components/FolderNav/DeleteFolderDialog.d.ts +16 -0
  93. package/dist/admin/src/components/ContentTypeBuilderNav/components/FolderNav/FolderNameField.d.ts +12 -0
  94. package/dist/admin/src/components/ContentTypeBuilderNav/components/FolderNav/FolderNavSection.d.ts +14 -0
  95. package/dist/admin/src/components/ContentTypeBuilderNav/components/FolderNav/SortableTreeRow.d.ts +38 -0
  96. package/dist/admin/src/components/ContentTypeBuilderNav/{useContentTypeBuilderMenu.d.ts → hooks/useContentTypeBuilderMenu.d.ts} +17 -7
  97. package/dist/admin/src/components/ContentTypeBuilderNav/hooks/useFolderActions.d.ts +42 -0
  98. package/dist/admin/src/components/ContentTypeBuilderNav/hooks/useSortableTree.d.ts +53 -0
  99. package/dist/admin/src/components/ContentTypeBuilderNav/lib/buildFolderTree.d.ts +42 -0
  100. package/dist/admin/src/components/ContentTypeBuilderNav/lib/dropProjection.d.ts +58 -0
  101. package/dist/admin/src/components/ContentTypeBuilderNav/lib/flatModel.d.ts +18 -0
  102. package/dist/admin/src/components/DataManager/DataManagerContext.d.ts +41 -0
  103. package/dist/admin/src/components/DataManager/reducer.d.ts +105 -1
  104. package/dist/admin/src/components/DataManager/utils/cleanData.d.ts +5 -1
  105. package/dist/admin/src/components/DataManager/utils/contentStructure.d.ts +68 -0
  106. package/dist/admin/src/components/FolderSelect.d.ts +18 -0
  107. package/dist/admin/src/components/FormModal/contentType/contentTypeForm.d.ts +0 -21
  108. package/dist/admin/src/components/FormModal/forms/forms.d.ts +0 -10
  109. package/dist/admin/src/components/FormModal/reducer.d.ts +7 -0
  110. package/dist/admin/src/utils/folderTelemetry.d.ts +5 -0
  111. package/dist/admin/translations/en.json.js +23 -0
  112. package/dist/admin/translations/en.json.js.map +1 -1
  113. package/dist/admin/translations/en.json.mjs +23 -0
  114. package/dist/admin/translations/en.json.mjs.map +1 -1
  115. package/dist/admin/utils/folderTelemetry.js +19 -0
  116. package/dist/admin/utils/folderTelemetry.js.map +1 -0
  117. package/dist/admin/utils/folderTelemetry.mjs +16 -0
  118. package/dist/admin/utils/folderTelemetry.mjs.map +1 -0
  119. package/dist/server/controllers/schema.js +1 -1
  120. package/dist/server/controllers/schema.js.map +1 -1
  121. package/dist/server/controllers/schema.mjs +1 -1
  122. package/dist/server/controllers/schema.mjs.map +1 -1
  123. package/dist/server/controllers/validation/common.js +3 -0
  124. package/dist/server/controllers/validation/common.js.map +1 -1
  125. package/dist/server/controllers/validation/common.mjs +3 -1
  126. package/dist/server/controllers/validation/common.mjs.map +1 -1
  127. package/dist/server/controllers/validation/content-structure.js +177 -0
  128. package/dist/server/controllers/validation/content-structure.js.map +1 -0
  129. package/dist/server/controllers/validation/content-structure.mjs +173 -0
  130. package/dist/server/controllers/validation/content-structure.mjs.map +1 -0
  131. package/dist/server/controllers/validation/schema.js +3 -1
  132. package/dist/server/controllers/validation/schema.js.map +1 -1
  133. package/dist/server/controllers/validation/schema.mjs +3 -1
  134. package/dist/server/controllers/validation/schema.mjs.map +1 -1
  135. package/dist/server/register.js +1 -1
  136. package/dist/server/register.js.map +1 -1
  137. package/dist/server/register.mjs +1 -1
  138. package/dist/server/register.mjs.map +1 -1
  139. package/dist/server/routes/content-api.js +4 -4
  140. package/dist/server/routes/content-api.js.map +1 -1
  141. package/dist/server/routes/content-api.mjs +4 -4
  142. package/dist/server/routes/content-api.mjs.map +1 -1
  143. package/dist/server/services/api-handler.js +37 -4
  144. package/dist/server/services/api-handler.js.map +1 -1
  145. package/dist/server/services/api-handler.mjs +36 -5
  146. package/dist/server/services/api-handler.mjs.map +1 -1
  147. package/dist/server/services/content-structure.js +174 -0
  148. package/dist/server/services/content-structure.js.map +1 -0
  149. package/dist/server/services/content-structure.mjs +172 -0
  150. package/dist/server/services/content-structure.mjs.map +1 -0
  151. package/dist/server/services/content-types.js +196 -44
  152. package/dist/server/services/content-types.js.map +1 -1
  153. package/dist/server/services/content-types.mjs +195 -45
  154. package/dist/server/services/content-types.mjs.map +1 -1
  155. package/dist/server/services/index.js +3 -1
  156. package/dist/server/services/index.js.map +1 -1
  157. package/dist/server/services/index.mjs +3 -1
  158. package/dist/server/services/index.mjs.map +1 -1
  159. package/dist/server/services/schema-builder/index.js +4 -2
  160. package/dist/server/services/schema-builder/index.js.map +1 -1
  161. package/dist/server/services/schema-builder/index.mjs +4 -2
  162. package/dist/server/services/schema-builder/index.mjs.map +1 -1
  163. package/dist/server/services/schema-builder/schema-handler.js +16 -16
  164. package/dist/server/services/schema-builder/schema-handler.js.map +1 -1
  165. package/dist/server/services/schema-builder/schema-handler.mjs +16 -16
  166. package/dist/server/services/schema-builder/schema-handler.mjs.map +1 -1
  167. package/dist/server/services/schema-mutation.js +46 -0
  168. package/dist/server/services/schema-mutation.js.map +1 -0
  169. package/dist/server/services/schema-mutation.mjs +43 -0
  170. package/dist/server/services/schema-mutation.mjs.map +1 -0
  171. package/dist/server/services/schema.js +134 -79
  172. package/dist/server/services/schema.js.map +1 -1
  173. package/dist/server/services/schema.mjs +135 -80
  174. package/dist/server/services/schema.mjs.map +1 -1
  175. package/dist/server/src/controllers/schema.d.ts.map +1 -1
  176. package/dist/server/src/controllers/validation/common.d.ts +1 -0
  177. package/dist/server/src/controllers/validation/common.d.ts.map +1 -1
  178. package/dist/server/src/controllers/validation/content-structure.d.ts +56 -0
  179. package/dist/server/src/controllers/validation/content-structure.d.ts.map +1 -0
  180. package/dist/server/src/controllers/validation/schema.d.ts +35 -0
  181. package/dist/server/src/controllers/validation/schema.d.ts.map +1 -1
  182. package/dist/server/src/index.d.ts +3 -0
  183. package/dist/server/src/index.d.ts.map +1 -1
  184. package/dist/server/src/routes/content-api.d.ts.map +1 -1
  185. package/dist/server/src/services/api-handler.d.ts +13 -1
  186. package/dist/server/src/services/api-handler.d.ts.map +1 -1
  187. package/dist/server/src/services/content-structure.d.ts +37 -0
  188. package/dist/server/src/services/content-structure.d.ts.map +1 -0
  189. package/dist/server/src/services/content-types.d.ts +4 -0
  190. package/dist/server/src/services/content-types.d.ts.map +1 -1
  191. package/dist/server/src/services/index.d.ts +4 -0
  192. package/dist/server/src/services/index.d.ts.map +1 -1
  193. package/dist/server/src/services/schema-builder/index.d.ts +3 -1
  194. package/dist/server/src/services/schema-builder/index.d.ts.map +1 -1
  195. package/dist/server/src/services/schema-builder/schema-handler.d.ts.map +1 -1
  196. package/dist/server/src/services/schema-mutation.d.ts +26 -0
  197. package/dist/server/src/services/schema-mutation.d.ts.map +1 -0
  198. package/dist/server/src/services/schema.d.ts +1 -0
  199. package/dist/server/src/services/schema.d.ts.map +1 -1
  200. package/dist/server/src/utils/index.d.ts +2 -0
  201. package/dist/server/src/utils/index.d.ts.map +1 -1
  202. package/dist/server/utils/index.js.map +1 -1
  203. package/dist/server/utils/index.mjs.map +1 -1
  204. package/package.json +6 -6
  205. package/dist/admin/components/ContentTypeBuilderNav/useContentTypeBuilderMenu.js +0 -157
  206. package/dist/admin/components/ContentTypeBuilderNav/useContentTypeBuilderMenu.js.map +0 -1
  207. package/dist/admin/components/ContentTypeBuilderNav/useContentTypeBuilderMenu.mjs +0 -151
  208. package/dist/admin/components/ContentTypeBuilderNav/useContentTypeBuilderMenu.mjs.map +0 -1
@@ -3,6 +3,7 @@
3
3
  var utils = require('@strapi/utils');
4
4
  var fp = require('lodash/fp');
5
5
  var index = require('./schema-builder/index.js');
6
+ var schemaMutation = require('./schema-mutation.js');
6
7
  var index$1 = require('../utils/index.js');
7
8
  var contentTypes = require('./content-types.js');
8
9
 
@@ -104,7 +105,10 @@ const getSchema = async ()=>{
104
105
  attributes: formatAttributes(component)
105
106
  };
106
107
  }, strapi.components);
108
+ const coreContentStructure = strapi.get('content-structure');
109
+ const contentStructure = await coreContentStructure.getCleanedFile();
107
110
  return {
111
+ contentStructure,
108
112
  contentTypes: contentTypes$1,
109
113
  components
110
114
  };
@@ -112,105 +116,156 @@ const getSchema = async ()=>{
112
116
  const updateSchema = async (schema)=>{
113
117
  const builder = index();
114
118
  const apiHandler = index$1.getService('api-handler');
115
- const { components, contentTypes } = schema;
119
+ const { components, contentTypes: contentTypes$1, contentStructure } = schema;
120
+ // Reject protected/plugin deletes before builders, API backups, or folder reconciliation can
121
+ // mutate anything. This is the server-side invariant for crafted update requests.
122
+ contentTypes$1.filter((contentType)=>contentType.action === 'delete').forEach((contentType)=>contentTypes.assertCTBOwnedApplicationContentType(contentType.uid));
116
123
  // pre-process data
117
124
  removeEmptyDefaultsOnUpdates(schema);
118
125
  removeDeletedUIDTargetFieldsOnUpdates(schema);
119
- // we pre create empty typesk
120
- for (const contentType of contentTypes){
121
- if (contentType.action === 'create') {
122
- builder.createContentType({
123
- ...contentType,
124
- attributes: {}
125
- });
126
+ const upsertedUids = new Map();
127
+ const deletedUids = new Set();
128
+ const generatedApiNames = [];
129
+ const backedUpApiUids = [];
130
+ let schemaAlreadyRolledBack = false;
131
+ const APIsToDelete = contentTypes$1.filter((contentType)=>contentType.action === 'delete').map((contentType)=>contentType.uid);
132
+ try {
133
+ for (const contentType of contentTypes$1){
134
+ if (contentType.action === 'create') {
135
+ upsertedUids.set(contentType.uid, contentType.kind ?? 'collectionType');
136
+ } else if (contentType.action === 'update' && contentType.kind) {
137
+ // A kind switch in the same save must override the registry's stale kind,
138
+ // otherwise a folder assignment into the new section fails validation.
139
+ upsertedUids.set(contentType.uid, contentType.kind);
140
+ } else if (contentType.action === 'delete') {
141
+ deletedUids.add(contentType.uid);
142
+ }
126
143
  }
127
- }
128
- // we pre create empty types
129
- for (const component of components){
130
- if (component.action === 'create') {
131
- builder.createComponent({
132
- ...component,
133
- attributes: {}
134
- });
144
+ // Validate folder references before anything is written. This will throw if invalid.
145
+ index$1.getService('content-structure').validateFromUpdate({
146
+ incomingStructure: contentStructure,
147
+ upsertedUids,
148
+ deletedUids
149
+ });
150
+ // we pre create empty typesk
151
+ for (const contentType of contentTypes$1){
152
+ if (contentType.action === 'create') {
153
+ builder.createContentType({
154
+ ...contentType,
155
+ attributes: {}
156
+ });
157
+ }
135
158
  }
136
- }
137
- for (const contentType of contentTypes){
138
- const { action, uid } = contentType;
139
- if (action === 'create') {
140
- builder.createContentTypeAttributes(uid, contentType.attributes.reduce((acc, attr)=>{
141
- acc[attr.name] = attr.properties;
142
- return acc;
143
- }, {}));
144
- if (!contentType.plugin) {
145
- await index$1.getService('content-types').generateAPI({
146
- displayName: contentType.displayName,
147
- singularName: contentType.singularName,
148
- pluralName: contentType.pluralName,
149
- kind: contentType.kind
159
+ // we pre create empty types
160
+ for (const component of components){
161
+ if (component.action === 'create') {
162
+ builder.createComponent({
163
+ ...component,
164
+ attributes: {}
150
165
  });
151
166
  }
152
167
  }
153
- if (action === 'update') {
154
- builder.editContentType({
155
- ...contentType,
156
- attributes: contentType.attributes.reduce((acc, attr)=>{
157
- // NOTE: handle renaming migrations here by comparing attr name & attr.properties.name
158
- if (attr.action === 'delete') {
159
- return acc;
160
- }
168
+ for (const contentType of contentTypes$1){
169
+ const { action, uid } = contentType;
170
+ if (action === 'create') {
171
+ builder.createContentTypeAttributes(uid, contentType.attributes.reduce((acc, attr)=>{
161
172
  acc[attr.name] = attr.properties;
162
173
  return acc;
163
- }, {})
164
- });
165
- }
166
- if (action === 'delete') {
167
- builder.deleteContentType(uid);
168
- await apiHandler.backup(uid);
169
- }
170
- }
171
- for (const component of components){
172
- const { action, uid } = component;
173
- if (action === 'create') {
174
- builder.createComponentAttributes(uid, component.attributes.reduce((acc, attr)=>{
175
- acc[attr.name] = attr.properties;
176
- return acc;
177
- }, {}));
178
- }
179
- if (action === 'update') {
180
- builder.editComponent({
181
- ...component,
182
- attributes: component.attributes.reduce((acc, attr)=>{
183
- if (attr.action === 'delete') {
174
+ }, {}));
175
+ if (!contentType.plugin) {
176
+ // Track before generation because a generator failure can leave a partial skeleton.
177
+ generatedApiNames.push(contentType.singularName);
178
+ await index$1.getService('content-types').generateAPI({
179
+ displayName: contentType.displayName,
180
+ singularName: contentType.singularName,
181
+ pluralName: contentType.pluralName,
182
+ kind: contentType.kind
183
+ });
184
+ }
185
+ }
186
+ if (action === 'update') {
187
+ builder.editContentType({
188
+ ...contentType,
189
+ attributes: contentType.attributes.reduce((acc, attr)=>{
190
+ // NOTE: handle renaming migrations here by comparing attr name & attr.properties.name
191
+ if (attr.action === 'delete') {
192
+ return acc;
193
+ }
194
+ acc[attr.name] = attr.properties;
184
195
  return acc;
185
- }
196
+ }, {})
197
+ });
198
+ }
199
+ if (action === 'delete') {
200
+ builder.deleteContentType(uid);
201
+ await apiHandler.backup(uid);
202
+ backedUpApiUids.push(uid);
203
+ }
204
+ }
205
+ for (const component of components){
206
+ const { action, uid } = component;
207
+ if (action === 'create') {
208
+ builder.createComponentAttributes(uid, component.attributes.reduce((acc, attr)=>{
186
209
  acc[attr.name] = attr.properties;
187
210
  return acc;
188
- }, {})
189
- });
211
+ }, {}));
212
+ }
213
+ if (action === 'update') {
214
+ builder.editComponent({
215
+ ...component,
216
+ attributes: component.attributes.reduce((acc, attr)=>{
217
+ if (attr.action === 'delete') {
218
+ return acc;
219
+ }
220
+ acc[attr.name] = attr.properties;
221
+ return acc;
222
+ }, {})
223
+ });
224
+ }
225
+ if (action === 'delete') {
226
+ builder.deleteComponent(uid);
227
+ }
190
228
  }
191
- if (action === 'delete') {
192
- builder.deleteComponent(uid);
229
+ // run sanity checks on the schema
230
+ // Relations target existing types
231
+ // Bidirectional relation have their counterpart in the schema
232
+ // Components target existing components
233
+ // Nested components target existing components
234
+ // Dynamic zones target existing components
235
+ const schemaFilesWritten = await builder.writeFiles();
236
+ if (!schemaFilesWritten) {
237
+ schemaAlreadyRolledBack = true;
238
+ throw new utils.errors.ApplicationError('Invalid schema edition');
193
239
  }
194
- }
195
- // run sanity checks on the schema
196
- // Relations target existing types
197
- // Bidirectional relation have their counterpart in the schema
198
- // Components target existing components
199
- // Nested components target existing components
200
- // Dynamic zones target existing components
201
- const APIsToDelete = contentTypes.filter((ct)=>ct.action === 'delete').map((ct)=>ct.uid);
202
- await builder.writeFiles();
203
- try {
204
240
  for (const uid of APIsToDelete){
205
- await apiHandler.clear(uid);
241
+ await apiHandler.clear(uid, {
242
+ preserveBackup: true
243
+ });
206
244
  }
245
+ // Commit the single folder file last. Every preceding filesystem mutation can be restored.
246
+ await index$1.getService('content-structure').commitFromUpdate({
247
+ incomingStructure: contentStructure,
248
+ deletedUids
249
+ });
207
250
  } catch (error) {
251
+ await schemaMutation.rollbackSchemaMutation({
252
+ builder,
253
+ apiHandler,
254
+ backedUpApiUids,
255
+ generatedApiNames,
256
+ schemaAlreadyRolledBack
257
+ });
258
+ throw error;
259
+ }
260
+ // Backup cleanup is deliberately outside the compensating boundary: at this point all
261
+ // user-visible artifacts, including groups.json, have committed.
262
+ for (const error of (await schemaMutation.finalizeSchemaMutation({
263
+ apiHandler,
264
+ backedUpApiUids
265
+ }))){
208
266
  strapi.log.error(error);
209
- for (const uid of APIsToDelete){
210
- await apiHandler.rollback(uid);
211
- }
212
267
  }
213
- for (const contentType of contentTypes){
268
+ for (const contentType of contentTypes$1){
214
269
  if (contentType.action === 'delete') {
215
270
  strapi.eventHub.emit('content-type.delete', {
216
271
  contentType: builder.contentTypes.get(contentType.uid)
@@ -1 +1 @@
1
- {"version":3,"file":"schema.js","sources":["../../../server/src/services/schema.ts"],"sourcesContent":["import { contentTypes as contentTypesUtils } from '@strapi/utils';\nimport { mapValues } from 'lodash/fp';\n\nimport type { Schema } from '@strapi/types';\n\nimport createBuilder from './schema-builder';\nimport { getService } from '../utils';\nimport type { Schema as CTBSchema } from '../controllers/validation/schema';\nimport { getRestrictRelationsTo, isContentTypeVisible } from './content-types';\n\nconst removeEmptyDefaultsOnUpdates = (schema: CTBSchema) => {\n schema.components.forEach((component) => {\n if (component.action === 'delete') {\n return;\n }\n\n component.attributes.forEach((attribute) => {\n if (attribute.action === 'update') {\n const { properties } = attribute;\n\n if ('default' in properties && properties.default === '') {\n properties.default = undefined;\n }\n }\n });\n });\n\n schema.contentTypes.forEach((contentType) => {\n if (contentType.action === 'delete') {\n return;\n }\n\n contentType.attributes.forEach((attribute) => {\n if (attribute.action === 'update') {\n const { properties } = attribute;\n\n if ('default' in properties && properties.default === '') {\n properties.default = undefined;\n }\n }\n });\n });\n};\n\nconst removeDeletedUIDTargetFieldsOnUpdates = (schema: CTBSchema) => {\n schema.contentTypes.forEach((contentType) => {\n if (contentType.action === 'delete') {\n return;\n }\n\n contentType.attributes.forEach((attribute) => {\n if (attribute.action === 'update') {\n const { properties } = attribute;\n\n if (\n properties.type === 'uid' &&\n properties.targetField &&\n !contentType.attributes.find((attr) => attr.name === properties.targetField)\n ) {\n properties.targetField = undefined;\n }\n }\n });\n });\n};\n\nconst formatAttributes = (model: any) => {\n const { getVisibleAttributes } = contentTypesUtils;\n\n // only get attributes that can be seen in the CTB\n return getVisibleAttributes(model).map((key) => {\n return { ...formatAttribute(model.attributes[key]), name: key };\n });\n};\n\nexport const formatAttribute = (attribute: Schema.Attribute.AnyAttribute & Record<string, any>) => {\n if (attribute.type === 'relation') {\n return {\n ...attribute,\n targetAttribute: attribute.inversedBy || attribute.mappedBy || null,\n // Explicitly preserve conditions if they exist\n ...(attribute.conditions && { conditions: attribute.conditions }),\n };\n }\n\n return attribute;\n};\n\nexport const getSchema = async () => {\n const contentTypes = mapValues((contentType) => {\n const {\n uid,\n options,\n globalId,\n pluginOptions,\n kind,\n modelName,\n plugin,\n collectionName,\n info,\n modelType,\n } = contentType;\n\n return {\n uid,\n modelName,\n kind,\n globalId,\n options,\n pluginOptions,\n plugin,\n collectionName,\n info,\n modelType,\n attributes: formatAttributes(contentType),\n visible: isContentTypeVisible(contentType),\n restrictRelationsTo: getRestrictRelationsTo(contentType),\n };\n }, strapi.contentTypes);\n\n const components = mapValues((component) => {\n const { uid, globalId, modelName, collectionName, info, category, modelType } = component;\n\n return {\n uid,\n modelName,\n globalId,\n modelType,\n collectionName,\n category,\n info,\n attributes: formatAttributes(component),\n };\n }, strapi.components);\n\n return {\n contentTypes,\n components,\n };\n};\n\nexport const updateSchema = async (schema: CTBSchema) => {\n const builder = createBuilder();\n const apiHandler = getService('api-handler');\n\n const { components, contentTypes } = schema;\n\n // pre-process data\n removeEmptyDefaultsOnUpdates(schema);\n removeDeletedUIDTargetFieldsOnUpdates(schema);\n\n // we pre create empty typesk\n for (const contentType of contentTypes) {\n if (contentType.action === 'create') {\n builder.createContentType({\n ...contentType,\n attributes: {},\n });\n }\n }\n\n // we pre create empty types\n for (const component of components) {\n if (component.action === 'create') {\n builder.createComponent({\n ...component,\n attributes: {},\n });\n }\n }\n\n for (const contentType of contentTypes) {\n const { action, uid } = contentType;\n\n if (action === 'create') {\n builder.createContentTypeAttributes(\n uid,\n contentType.attributes.reduce((acc: any, attr: any) => {\n acc[attr.name] = attr.properties;\n return acc;\n }, {})\n );\n\n if (!contentType.plugin) {\n await getService('content-types').generateAPI({\n displayName: contentType!.displayName,\n singularName: contentType!.singularName,\n pluralName: contentType!.pluralName,\n kind: contentType!.kind,\n });\n }\n }\n\n if (action === 'update') {\n builder.editContentType({\n ...contentType,\n attributes: contentType.attributes.reduce((acc: any, attr: any) => {\n // NOTE: handle renaming migrations here by comparing attr name & attr.properties.name\n\n if (attr.action === 'delete') {\n return acc;\n }\n\n acc[attr.name] = attr.properties;\n return acc;\n }, {}),\n });\n }\n\n if (action === 'delete') {\n builder.deleteContentType(uid);\n await apiHandler.backup(uid);\n }\n }\n\n for (const component of components) {\n const { action, uid } = component;\n\n if (action === 'create') {\n builder.createComponentAttributes(\n uid,\n component.attributes.reduce((acc: any, attr: any) => {\n acc[attr.name] = attr.properties;\n return acc;\n }, {})\n );\n }\n\n if (action === 'update') {\n builder.editComponent({\n ...component,\n attributes: component.attributes.reduce((acc: any, attr: any) => {\n if (attr.action === 'delete') {\n return acc;\n }\n\n acc[attr.name] = attr.properties;\n return acc;\n }, {}),\n });\n }\n\n if (action === 'delete') {\n builder.deleteComponent(uid);\n }\n }\n\n // run sanity checks on the schema\n // Relations target existing types\n // Bidirectional relation have their counterpart in the schema\n // Components target existing components\n // Nested components target existing components\n // Dynamic zones target existing components\n\n const APIsToDelete = contentTypes\n .filter((ct: any) => ct.action === 'delete')\n .map((ct: any) => ct.uid);\n\n await builder.writeFiles();\n\n try {\n for (const uid of APIsToDelete) {\n await apiHandler.clear(uid);\n }\n } catch (error) {\n strapi.log.error(error);\n for (const uid of APIsToDelete) {\n await apiHandler.rollback(uid);\n }\n }\n\n for (const contentType of contentTypes) {\n if (contentType.action === 'delete') {\n strapi.eventHub.emit('content-type.delete', {\n contentType: builder.contentTypes.get(contentType.uid),\n });\n }\n\n if (contentType.action === 'update') {\n strapi.eventHub.emit('content-type.update', {\n contentType: builder.contentTypes.get(contentType.uid),\n });\n }\n\n if (contentType.action === 'create') {\n strapi.eventHub.emit('content-type.create', {\n contentType: builder.contentTypes.get(contentType.uid),\n });\n }\n }\n\n for (const component of components) {\n if (component.action === 'delete') {\n strapi.eventHub.emit('component.delete', {\n component: builder.components.get(component.uid),\n });\n }\n\n if (component.action === 'update') {\n strapi.eventHub.emit('component.update', {\n component: builder.components.get(component.uid),\n });\n }\n\n if (component.action === 'create') {\n strapi.eventHub.emit('component.create', {\n component: builder.components.get(component.uid),\n });\n }\n }\n};\n"],"names":["removeEmptyDefaultsOnUpdates","schema","components","forEach","component","action","attributes","attribute","properties","default","undefined","contentTypes","contentType","removeDeletedUIDTargetFieldsOnUpdates","type","targetField","find","attr","name","formatAttributes","model","getVisibleAttributes","contentTypesUtils","map","key","formatAttribute","targetAttribute","inversedBy","mappedBy","conditions","getSchema","mapValues","uid","options","globalId","pluginOptions","kind","modelName","plugin","collectionName","info","modelType","visible","isContentTypeVisible","restrictRelationsTo","getRestrictRelationsTo","strapi","category","updateSchema","builder","createBuilder","apiHandler","getService","createContentType","createComponent","createContentTypeAttributes","reduce","acc","generateAPI","displayName","singularName","pluralName","editContentType","deleteContentType","backup","createComponentAttributes","editComponent","deleteComponent","APIsToDelete","filter","ct","writeFiles","clear","error","log","rollback","eventHub","emit","get"],"mappings":"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"}
1
+ {"version":3,"file":"schema.js","sources":["../../../server/src/services/schema.ts"],"sourcesContent":["import { contentTypes as contentTypesUtils, errors } from '@strapi/utils';\nimport { mapValues } from 'lodash/fp';\n\nimport type { Schema } from '@strapi/types';\n\nimport createBuilder from './schema-builder';\nimport { finalizeSchemaMutation, rollbackSchemaMutation } from './schema-mutation';\nimport { getService } from '../utils';\nimport type { Schema as CTBSchema } from '../controllers/validation/schema';\nimport {\n assertCTBOwnedApplicationContentType,\n getRestrictRelationsTo,\n isContentTypeVisible,\n} from './content-types';\nimport type { CoreContentStructureService } from './content-structure';\n\ntype ContentTypeKind = 'collectionType' | 'singleType';\n\nconst removeEmptyDefaultsOnUpdates = (schema: CTBSchema) => {\n schema.components.forEach((component) => {\n if (component.action === 'delete') {\n return;\n }\n\n component.attributes.forEach((attribute) => {\n if (attribute.action === 'update') {\n const { properties } = attribute;\n\n if ('default' in properties && properties.default === '') {\n properties.default = undefined;\n }\n }\n });\n });\n\n schema.contentTypes.forEach((contentType) => {\n if (contentType.action === 'delete') {\n return;\n }\n\n contentType.attributes.forEach((attribute) => {\n if (attribute.action === 'update') {\n const { properties } = attribute;\n\n if ('default' in properties && properties.default === '') {\n properties.default = undefined;\n }\n }\n });\n });\n};\n\nconst removeDeletedUIDTargetFieldsOnUpdates = (schema: CTBSchema) => {\n schema.contentTypes.forEach((contentType) => {\n if (contentType.action === 'delete') {\n return;\n }\n\n contentType.attributes.forEach((attribute) => {\n if (attribute.action === 'update') {\n const { properties } = attribute;\n\n if (\n properties.type === 'uid' &&\n properties.targetField &&\n !contentType.attributes.find((attr) => attr.name === properties.targetField)\n ) {\n properties.targetField = undefined;\n }\n }\n });\n });\n};\n\nconst formatAttributes = (model: any) => {\n const { getVisibleAttributes } = contentTypesUtils;\n\n // only get attributes that can be seen in the CTB\n return getVisibleAttributes(model).map((key) => {\n return { ...formatAttribute(model.attributes[key]), name: key };\n });\n};\n\nexport const formatAttribute = (attribute: Schema.Attribute.AnyAttribute & Record<string, any>) => {\n if (attribute.type === 'relation') {\n return {\n ...attribute,\n targetAttribute: attribute.inversedBy || attribute.mappedBy || null,\n // Explicitly preserve conditions if they exist\n ...(attribute.conditions && { conditions: attribute.conditions }),\n };\n }\n\n return attribute;\n};\n\nexport const getSchema = async () => {\n const contentTypes = mapValues((contentType) => {\n const {\n uid,\n options,\n globalId,\n pluginOptions,\n kind,\n modelName,\n plugin,\n collectionName,\n info,\n modelType,\n } = contentType;\n\n return {\n uid,\n modelName,\n kind,\n globalId,\n options,\n pluginOptions,\n plugin,\n collectionName,\n info,\n modelType,\n attributes: formatAttributes(contentType),\n visible: isContentTypeVisible(contentType),\n restrictRelationsTo: getRestrictRelationsTo(contentType),\n };\n }, strapi.contentTypes);\n\n const components = mapValues((component) => {\n const { uid, globalId, modelName, collectionName, info, category, modelType } = component;\n\n return {\n uid,\n modelName,\n globalId,\n modelType,\n collectionName,\n category,\n info,\n attributes: formatAttributes(component),\n };\n }, strapi.components);\n\n const coreContentStructure: CoreContentStructureService = strapi.get('content-structure');\n const contentStructure = await coreContentStructure.getCleanedFile();\n\n return {\n contentStructure,\n contentTypes,\n components,\n };\n};\n\nexport const updateSchema = async (schema: CTBSchema) => {\n const builder = createBuilder();\n const apiHandler = getService('api-handler') as typeof import('./api-handler');\n\n const { components, contentTypes, contentStructure } = schema;\n\n // Reject protected/plugin deletes before builders, API backups, or folder reconciliation can\n // mutate anything. This is the server-side invariant for crafted update requests.\n contentTypes\n .filter((contentType) => contentType.action === 'delete')\n .forEach((contentType) => assertCTBOwnedApplicationContentType(contentType.uid));\n\n // pre-process data\n removeEmptyDefaultsOnUpdates(schema);\n removeDeletedUIDTargetFieldsOnUpdates(schema);\n\n const upsertedUids = new Map<string, ContentTypeKind>();\n const deletedUids = new Set<string>();\n const generatedApiNames: string[] = [];\n const backedUpApiUids: string[] = [];\n let schemaAlreadyRolledBack = false;\n const APIsToDelete = contentTypes\n .filter((contentType) => contentType.action === 'delete')\n .map((contentType) => contentType.uid);\n\n try {\n for (const contentType of contentTypes) {\n if (contentType.action === 'create') {\n upsertedUids.set(contentType.uid, contentType.kind ?? 'collectionType');\n } else if (contentType.action === 'update' && contentType.kind) {\n // A kind switch in the same save must override the registry's stale kind,\n // otherwise a folder assignment into the new section fails validation.\n upsertedUids.set(contentType.uid, contentType.kind);\n } else if (contentType.action === 'delete') {\n deletedUids.add(contentType.uid);\n }\n }\n\n // Validate folder references before anything is written. This will throw if invalid.\n getService('content-structure').validateFromUpdate({\n incomingStructure: contentStructure,\n upsertedUids,\n deletedUids,\n });\n\n // we pre create empty typesk\n for (const contentType of contentTypes) {\n if (contentType.action === 'create') {\n builder.createContentType({\n ...contentType,\n attributes: {},\n });\n }\n }\n\n // we pre create empty types\n for (const component of components) {\n if (component.action === 'create') {\n builder.createComponent({\n ...component,\n attributes: {},\n });\n }\n }\n\n for (const contentType of contentTypes) {\n const { action, uid } = contentType;\n\n if (action === 'create') {\n builder.createContentTypeAttributes(\n uid,\n contentType.attributes.reduce((acc: any, attr: any) => {\n acc[attr.name] = attr.properties;\n return acc;\n }, {})\n );\n\n if (!contentType.plugin) {\n // Track before generation because a generator failure can leave a partial skeleton.\n generatedApiNames.push(contentType.singularName);\n await getService('content-types').generateAPI({\n displayName: contentType!.displayName,\n singularName: contentType!.singularName,\n pluralName: contentType!.pluralName,\n kind: contentType!.kind,\n });\n }\n }\n\n if (action === 'update') {\n builder.editContentType({\n ...contentType,\n attributes: contentType.attributes.reduce((acc: any, attr: any) => {\n // NOTE: handle renaming migrations here by comparing attr name & attr.properties.name\n\n if (attr.action === 'delete') {\n return acc;\n }\n\n acc[attr.name] = attr.properties;\n return acc;\n }, {}),\n });\n }\n\n if (action === 'delete') {\n builder.deleteContentType(uid);\n await apiHandler.backup(uid);\n backedUpApiUids.push(uid);\n }\n }\n\n for (const component of components) {\n const { action, uid } = component;\n\n if (action === 'create') {\n builder.createComponentAttributes(\n uid,\n component.attributes.reduce((acc: any, attr: any) => {\n acc[attr.name] = attr.properties;\n return acc;\n }, {})\n );\n }\n\n if (action === 'update') {\n builder.editComponent({\n ...component,\n attributes: component.attributes.reduce((acc: any, attr: any) => {\n if (attr.action === 'delete') {\n return acc;\n }\n\n acc[attr.name] = attr.properties;\n return acc;\n }, {}),\n });\n }\n\n if (action === 'delete') {\n builder.deleteComponent(uid);\n }\n }\n\n // run sanity checks on the schema\n // Relations target existing types\n // Bidirectional relation have their counterpart in the schema\n // Components target existing components\n // Nested components target existing components\n // Dynamic zones target existing components\n\n const schemaFilesWritten = await builder.writeFiles();\n\n if (!schemaFilesWritten) {\n schemaAlreadyRolledBack = true;\n throw new errors.ApplicationError('Invalid schema edition');\n }\n\n for (const uid of APIsToDelete) {\n await apiHandler.clear(uid, { preserveBackup: true });\n }\n\n // Commit the single folder file last. Every preceding filesystem mutation can be restored.\n await getService('content-structure').commitFromUpdate({\n incomingStructure: contentStructure,\n deletedUids,\n });\n } catch (error) {\n await rollbackSchemaMutation({\n builder,\n apiHandler,\n backedUpApiUids,\n generatedApiNames,\n schemaAlreadyRolledBack,\n });\n\n throw error;\n }\n\n // Backup cleanup is deliberately outside the compensating boundary: at this point all\n // user-visible artifacts, including groups.json, have committed.\n for (const error of await finalizeSchemaMutation({ apiHandler, backedUpApiUids })) {\n strapi.log.error(error);\n }\n\n for (const contentType of contentTypes) {\n if (contentType.action === 'delete') {\n strapi.eventHub.emit('content-type.delete', {\n contentType: builder.contentTypes.get(contentType.uid),\n });\n }\n\n if (contentType.action === 'update') {\n strapi.eventHub.emit('content-type.update', {\n contentType: builder.contentTypes.get(contentType.uid),\n });\n }\n\n if (contentType.action === 'create') {\n strapi.eventHub.emit('content-type.create', {\n contentType: builder.contentTypes.get(contentType.uid),\n });\n }\n }\n\n for (const component of components) {\n if (component.action === 'delete') {\n strapi.eventHub.emit('component.delete', {\n component: builder.components.get(component.uid),\n });\n }\n\n if (component.action === 'update') {\n strapi.eventHub.emit('component.update', {\n component: builder.components.get(component.uid),\n });\n }\n\n if (component.action === 'create') {\n strapi.eventHub.emit('component.create', {\n component: builder.components.get(component.uid),\n });\n }\n }\n};\n"],"names":["removeEmptyDefaultsOnUpdates","schema","components","forEach","component","action","attributes","attribute","properties","default","undefined","contentTypes","contentType","removeDeletedUIDTargetFieldsOnUpdates","type","targetField","find","attr","name","formatAttributes","model","getVisibleAttributes","contentTypesUtils","map","key","formatAttribute","targetAttribute","inversedBy","mappedBy","conditions","getSchema","mapValues","uid","options","globalId","pluginOptions","kind","modelName","plugin","collectionName","info","modelType","visible","isContentTypeVisible","restrictRelationsTo","getRestrictRelationsTo","strapi","category","coreContentStructure","get","contentStructure","getCleanedFile","updateSchema","builder","createBuilder","apiHandler","getService","filter","assertCTBOwnedApplicationContentType","upsertedUids","Map","deletedUids","Set","generatedApiNames","backedUpApiUids","schemaAlreadyRolledBack","APIsToDelete","set","add","validateFromUpdate","incomingStructure","createContentType","createComponent","createContentTypeAttributes","reduce","acc","push","singularName","generateAPI","displayName","pluralName","editContentType","deleteContentType","backup","createComponentAttributes","editComponent","deleteComponent","schemaFilesWritten","writeFiles","errors","ApplicationError","clear","preserveBackup","commitFromUpdate","error","rollbackSchemaMutation","finalizeSchemaMutation","log","eventHub","emit"],"mappings":"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"}
@@ -1,8 +1,9 @@
1
- import { contentTypes } from '@strapi/utils';
1
+ import { errors, contentTypes } from '@strapi/utils';
2
2
  import { mapValues } from 'lodash/fp';
3
3
  import createBuilder from './schema-builder/index.mjs';
4
+ import { rollbackSchemaMutation, finalizeSchemaMutation } from './schema-mutation.mjs';
4
5
  import { getService } from '../utils/index.mjs';
5
- import { getRestrictRelationsTo, isContentTypeVisible } from './content-types.mjs';
6
+ import { getRestrictRelationsTo, isContentTypeVisible, assertCTBOwnedApplicationContentType } from './content-types.mjs';
6
7
 
7
8
  const removeEmptyDefaultsOnUpdates = (schema)=>{
8
9
  schema.components.forEach((component)=>{
@@ -102,7 +103,10 @@ const getSchema = async ()=>{
102
103
  attributes: formatAttributes(component)
103
104
  };
104
105
  }, strapi.components);
106
+ const coreContentStructure = strapi.get('content-structure');
107
+ const contentStructure = await coreContentStructure.getCleanedFile();
105
108
  return {
109
+ contentStructure,
106
110
  contentTypes,
107
111
  components
108
112
  };
@@ -110,103 +114,154 @@ const getSchema = async ()=>{
110
114
  const updateSchema = async (schema)=>{
111
115
  const builder = createBuilder();
112
116
  const apiHandler = getService('api-handler');
113
- const { components, contentTypes } = schema;
117
+ const { components, contentTypes, contentStructure } = schema;
118
+ // Reject protected/plugin deletes before builders, API backups, or folder reconciliation can
119
+ // mutate anything. This is the server-side invariant for crafted update requests.
120
+ contentTypes.filter((contentType)=>contentType.action === 'delete').forEach((contentType)=>assertCTBOwnedApplicationContentType(contentType.uid));
114
121
  // pre-process data
115
122
  removeEmptyDefaultsOnUpdates(schema);
116
123
  removeDeletedUIDTargetFieldsOnUpdates(schema);
117
- // we pre create empty typesk
118
- for (const contentType of contentTypes){
119
- if (contentType.action === 'create') {
120
- builder.createContentType({
121
- ...contentType,
122
- attributes: {}
123
- });
124
+ const upsertedUids = new Map();
125
+ const deletedUids = new Set();
126
+ const generatedApiNames = [];
127
+ const backedUpApiUids = [];
128
+ let schemaAlreadyRolledBack = false;
129
+ const APIsToDelete = contentTypes.filter((contentType)=>contentType.action === 'delete').map((contentType)=>contentType.uid);
130
+ try {
131
+ for (const contentType of contentTypes){
132
+ if (contentType.action === 'create') {
133
+ upsertedUids.set(contentType.uid, contentType.kind ?? 'collectionType');
134
+ } else if (contentType.action === 'update' && contentType.kind) {
135
+ // A kind switch in the same save must override the registry's stale kind,
136
+ // otherwise a folder assignment into the new section fails validation.
137
+ upsertedUids.set(contentType.uid, contentType.kind);
138
+ } else if (contentType.action === 'delete') {
139
+ deletedUids.add(contentType.uid);
140
+ }
124
141
  }
125
- }
126
- // we pre create empty types
127
- for (const component of components){
128
- if (component.action === 'create') {
129
- builder.createComponent({
130
- ...component,
131
- attributes: {}
132
- });
142
+ // Validate folder references before anything is written. This will throw if invalid.
143
+ getService('content-structure').validateFromUpdate({
144
+ incomingStructure: contentStructure,
145
+ upsertedUids,
146
+ deletedUids
147
+ });
148
+ // we pre create empty typesk
149
+ for (const contentType of contentTypes){
150
+ if (contentType.action === 'create') {
151
+ builder.createContentType({
152
+ ...contentType,
153
+ attributes: {}
154
+ });
155
+ }
133
156
  }
134
- }
135
- for (const contentType of contentTypes){
136
- const { action, uid } = contentType;
137
- if (action === 'create') {
138
- builder.createContentTypeAttributes(uid, contentType.attributes.reduce((acc, attr)=>{
139
- acc[attr.name] = attr.properties;
140
- return acc;
141
- }, {}));
142
- if (!contentType.plugin) {
143
- await getService('content-types').generateAPI({
144
- displayName: contentType.displayName,
145
- singularName: contentType.singularName,
146
- pluralName: contentType.pluralName,
147
- kind: contentType.kind
157
+ // we pre create empty types
158
+ for (const component of components){
159
+ if (component.action === 'create') {
160
+ builder.createComponent({
161
+ ...component,
162
+ attributes: {}
148
163
  });
149
164
  }
150
165
  }
151
- if (action === 'update') {
152
- builder.editContentType({
153
- ...contentType,
154
- attributes: contentType.attributes.reduce((acc, attr)=>{
155
- // NOTE: handle renaming migrations here by comparing attr name & attr.properties.name
156
- if (attr.action === 'delete') {
157
- return acc;
158
- }
166
+ for (const contentType of contentTypes){
167
+ const { action, uid } = contentType;
168
+ if (action === 'create') {
169
+ builder.createContentTypeAttributes(uid, contentType.attributes.reduce((acc, attr)=>{
159
170
  acc[attr.name] = attr.properties;
160
171
  return acc;
161
- }, {})
162
- });
163
- }
164
- if (action === 'delete') {
165
- builder.deleteContentType(uid);
166
- await apiHandler.backup(uid);
167
- }
168
- }
169
- for (const component of components){
170
- const { action, uid } = component;
171
- if (action === 'create') {
172
- builder.createComponentAttributes(uid, component.attributes.reduce((acc, attr)=>{
173
- acc[attr.name] = attr.properties;
174
- return acc;
175
- }, {}));
176
- }
177
- if (action === 'update') {
178
- builder.editComponent({
179
- ...component,
180
- attributes: component.attributes.reduce((acc, attr)=>{
181
- if (attr.action === 'delete') {
172
+ }, {}));
173
+ if (!contentType.plugin) {
174
+ // Track before generation because a generator failure can leave a partial skeleton.
175
+ generatedApiNames.push(contentType.singularName);
176
+ await getService('content-types').generateAPI({
177
+ displayName: contentType.displayName,
178
+ singularName: contentType.singularName,
179
+ pluralName: contentType.pluralName,
180
+ kind: contentType.kind
181
+ });
182
+ }
183
+ }
184
+ if (action === 'update') {
185
+ builder.editContentType({
186
+ ...contentType,
187
+ attributes: contentType.attributes.reduce((acc, attr)=>{
188
+ // NOTE: handle renaming migrations here by comparing attr name & attr.properties.name
189
+ if (attr.action === 'delete') {
190
+ return acc;
191
+ }
192
+ acc[attr.name] = attr.properties;
182
193
  return acc;
183
- }
194
+ }, {})
195
+ });
196
+ }
197
+ if (action === 'delete') {
198
+ builder.deleteContentType(uid);
199
+ await apiHandler.backup(uid);
200
+ backedUpApiUids.push(uid);
201
+ }
202
+ }
203
+ for (const component of components){
204
+ const { action, uid } = component;
205
+ if (action === 'create') {
206
+ builder.createComponentAttributes(uid, component.attributes.reduce((acc, attr)=>{
184
207
  acc[attr.name] = attr.properties;
185
208
  return acc;
186
- }, {})
187
- });
209
+ }, {}));
210
+ }
211
+ if (action === 'update') {
212
+ builder.editComponent({
213
+ ...component,
214
+ attributes: component.attributes.reduce((acc, attr)=>{
215
+ if (attr.action === 'delete') {
216
+ return acc;
217
+ }
218
+ acc[attr.name] = attr.properties;
219
+ return acc;
220
+ }, {})
221
+ });
222
+ }
223
+ if (action === 'delete') {
224
+ builder.deleteComponent(uid);
225
+ }
188
226
  }
189
- if (action === 'delete') {
190
- builder.deleteComponent(uid);
227
+ // run sanity checks on the schema
228
+ // Relations target existing types
229
+ // Bidirectional relation have their counterpart in the schema
230
+ // Components target existing components
231
+ // Nested components target existing components
232
+ // Dynamic zones target existing components
233
+ const schemaFilesWritten = await builder.writeFiles();
234
+ if (!schemaFilesWritten) {
235
+ schemaAlreadyRolledBack = true;
236
+ throw new errors.ApplicationError('Invalid schema edition');
191
237
  }
192
- }
193
- // run sanity checks on the schema
194
- // Relations target existing types
195
- // Bidirectional relation have their counterpart in the schema
196
- // Components target existing components
197
- // Nested components target existing components
198
- // Dynamic zones target existing components
199
- const APIsToDelete = contentTypes.filter((ct)=>ct.action === 'delete').map((ct)=>ct.uid);
200
- await builder.writeFiles();
201
- try {
202
238
  for (const uid of APIsToDelete){
203
- await apiHandler.clear(uid);
239
+ await apiHandler.clear(uid, {
240
+ preserveBackup: true
241
+ });
204
242
  }
243
+ // Commit the single folder file last. Every preceding filesystem mutation can be restored.
244
+ await getService('content-structure').commitFromUpdate({
245
+ incomingStructure: contentStructure,
246
+ deletedUids
247
+ });
205
248
  } catch (error) {
249
+ await rollbackSchemaMutation({
250
+ builder,
251
+ apiHandler,
252
+ backedUpApiUids,
253
+ generatedApiNames,
254
+ schemaAlreadyRolledBack
255
+ });
256
+ throw error;
257
+ }
258
+ // Backup cleanup is deliberately outside the compensating boundary: at this point all
259
+ // user-visible artifacts, including groups.json, have committed.
260
+ for (const error of (await finalizeSchemaMutation({
261
+ apiHandler,
262
+ backedUpApiUids
263
+ }))){
206
264
  strapi.log.error(error);
207
- for (const uid of APIsToDelete){
208
- await apiHandler.rollback(uid);
209
- }
210
265
  }
211
266
  for (const contentType of contentTypes){
212
267
  if (contentType.action === 'delete') {