@nitrogenbuilder/connector-payload 1.0.0 → 1.1.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.
@@ -6,12 +6,7 @@ export declare const NITROGEN_COMPONENT_CATALOG_COLLECTION = "nitrogen-component
6
6
  export declare const NITROGEN_COMPONENT_USAGE_COLLECTION = "nitrogen-component-usage";
7
7
  type ComponentUsageRecord = {
8
8
  componentName: string;
9
- depth: number;
10
9
  moduleId: string;
11
- modulePath: string;
12
- parentComponentName?: string;
13
- parentModuleId?: string;
14
- parentModulePath?: string;
15
10
  };
16
11
  type ReindexResult = {
17
12
  catalogCount: number;
@@ -10,50 +10,41 @@ function moduleNameFromNode(node) {
10
10
  }
11
11
  return null;
12
12
  }
13
- function collectUsageFromTree(modules, pathPrefix, results, parentContext, depth = 0) {
13
+ function collectUsageFromTree(modules, pathPrefix, results) {
14
14
  if (!Array.isArray(modules))
15
15
  return;
16
16
  modules.forEach((maybeNode, index) => {
17
17
  if (!maybeNode || typeof maybeNode !== 'object')
18
18
  return;
19
19
  const node = maybeNode;
20
- const modulePath = `${pathPrefix}[${index}]`;
21
20
  const componentName = moduleNameFromNode(node);
21
+ // The tree path is no longer stored, but it is still the identity of last
22
+ // resort for a node that carries no id of its own. Such a node reverts to
23
+ // the old positional behaviour and churns when the tree is reordered.
24
+ const modulePath = `${pathPrefix}[${index}]`;
22
25
  const moduleId = typeof node.id === 'string' || typeof node.id === 'number'
23
26
  ? String(node.id)
24
27
  : modulePath;
25
- const currentContext = componentName
26
- ? {
27
- componentName,
28
- moduleId,
29
- modulePath,
30
- }
31
- : parentContext;
32
28
  if (componentName) {
33
29
  results.push({
34
30
  componentName,
35
- depth,
36
31
  moduleId,
37
- modulePath,
38
- parentComponentName: parentContext?.componentName,
39
- parentModuleId: parentContext?.moduleId,
40
- parentModulePath: parentContext?.modulePath,
41
32
  });
42
33
  }
43
34
  const props = node.props;
44
35
  if (props && typeof props === 'object') {
45
36
  const children = props.children;
46
37
  if (Array.isArray(children)) {
47
- collectUsageFromTree(children, `${modulePath}.props.children`, results, currentContext, depth + 1);
38
+ collectUsageFromTree(children, `${modulePath}.props.children`, results);
48
39
  }
49
40
  else if (children && typeof children === 'object') {
50
41
  for (const [slotName, slotChildren] of Object.entries(children)) {
51
- collectUsageFromTree(slotChildren, `${modulePath}.props.children.${slotName}`, results, currentContext, depth + 1);
42
+ collectUsageFromTree(slotChildren, `${modulePath}.props.children.${slotName}`, results);
52
43
  }
53
44
  }
54
45
  }
55
46
  if (Array.isArray(node.children)) {
56
- collectUsageFromTree(node.children, `${modulePath}.children`, results, currentContext, depth + 1);
47
+ collectUsageFromTree(node.children, `${modulePath}.children`, results);
57
48
  }
58
49
  });
59
50
  }
@@ -84,62 +75,77 @@ export async function findAllDocs(payload, collection, options, req) {
84
75
  }
85
76
  return docs;
86
77
  }
87
- async function deleteUsageDocs(payload, docs, req) {
88
- for (const doc of docs) {
89
- await payload.delete({
90
- collection: NITROGEN_COMPONENT_USAGE_COLLECTION,
91
- id: doc.id,
92
- overrideAccess: true,
93
- req,
94
- });
95
- }
78
+ /**
79
+ * Delete usage rows in one bulk statement rather than paginating the ids and
80
+ * issuing a `payload.delete` per row. Usage is rebuilt wholesale on every save,
81
+ * so a document with N modules previously cost N deletes (plus the finds to
82
+ * discover them) on the critical path of the save transaction.
83
+ */
84
+ async function deleteUsageDocsWhere(payload, where, req) {
85
+ await payload.delete({
86
+ collection: NITROGEN_COMPONENT_USAGE_COLLECTION,
87
+ overrideAccess: true,
88
+ req,
89
+ where,
90
+ });
96
91
  }
97
- async function findUsageDocsForSource(payload, sourceCollection, sourceDocumentId, req) {
98
- return findAllDocs(payload, NITROGEN_COMPONENT_USAGE_COLLECTION, {
99
- limit: 100,
100
- select: {
101
- id: true,
102
- },
103
- where: {
104
- and: [
105
- {
106
- sourceCollection: {
107
- equals: sourceCollection,
108
- },
92
+ function usageDocsForSourceWhere(sourceCollection, sourceDocumentId) {
93
+ return {
94
+ and: [
95
+ {
96
+ sourceCollection: {
97
+ equals: sourceCollection,
109
98
  },
110
- {
111
- sourceDocumentId: {
112
- equals: sourceDocumentId,
113
- },
99
+ },
100
+ {
101
+ sourceDocumentId: {
102
+ equals: sourceDocumentId,
114
103
  },
115
- ],
116
- },
117
- }, req);
104
+ },
105
+ ],
106
+ };
107
+ }
108
+ function usageKeyFor(sourceCollection, sourceDocumentId, record) {
109
+ return `${sourceCollection}:${sourceDocumentId}:${record.moduleId}:${record.componentName}`;
110
+ }
111
+ function usageSourceFields(doc) {
112
+ return {
113
+ sourceTitle: doc.title || '',
114
+ sourceSlug: doc.slug || '',
115
+ sourceStatus: doc.status || doc._status || '',
116
+ };
117
+ }
118
+ /**
119
+ * `componentName`, `moduleId`, `sourceCollection` and `sourceDocumentId` are all
120
+ * encoded in the usage key, so a key match already implies they agree; the
121
+ * source fields are reconciled separately by a single bulk update. That leaves
122
+ * only `sourceType`, which is checked defensively so a row written with the
123
+ * wrong value can still heal.
124
+ */
125
+ function usageRowIsCurrent(row, sourceType) {
126
+ return row.sourceType === sourceType;
127
+ }
128
+ function usageRowData(sourceCollection, sourceType, sourceDocumentId, sourceFields, record) {
129
+ return {
130
+ usageKey: usageKeyFor(sourceCollection, sourceDocumentId, record),
131
+ componentName: record.componentName,
132
+ sourceCollection,
133
+ sourceType,
134
+ sourceDocumentId,
135
+ ...sourceFields,
136
+ moduleId: record.moduleId,
137
+ };
118
138
  }
119
139
  async function createUsageDocs(payload, sourceCollection, sourceType, doc, req) {
120
140
  const sourceDocumentId = String(doc.id);
141
+ const sourceFields = usageSourceFields(doc);
121
142
  const usageRecords = extractComponentUsageRecords(doc.nitrogenData);
122
143
  for (const record of usageRecords) {
123
144
  await payload.create({
124
145
  collection: NITROGEN_COMPONENT_USAGE_COLLECTION,
125
146
  overrideAccess: true,
126
147
  req,
127
- data: {
128
- usageKey: `${sourceCollection}:${sourceDocumentId}:${record.moduleId}:${record.modulePath}:${record.componentName}`,
129
- componentName: record.componentName,
130
- depth: record.depth,
131
- sourceCollection,
132
- sourceType,
133
- sourceDocumentId,
134
- sourceTitle: doc.title || '',
135
- sourceSlug: doc.slug || '',
136
- sourceStatus: doc.status || doc._status || '',
137
- moduleId: record.moduleId,
138
- modulePath: record.modulePath,
139
- parentComponentName: record.parentComponentName || '',
140
- parentModuleId: record.parentModuleId || '',
141
- parentModulePath: record.parentModulePath || '',
142
- },
148
+ data: usageRowData(sourceCollection, sourceType, sourceDocumentId, sourceFields, record),
143
149
  });
144
150
  }
145
151
  return usageRecords.length;
@@ -156,7 +162,8 @@ export async function syncComponentCatalog(payload, manifestInput, req) {
156
162
  componentName: entry.name,
157
163
  scope: entry.scope || '',
158
164
  sidebarCategory: entry.sidebarCategory || '',
159
- icon: entry.icon || '',
165
+ // Icons are no longer carried on the manifest entry; they're assigned in
166
+ // the editor (Settings > Component Icons) and stored in NitrogenSettings.
160
167
  definition: entry,
161
168
  source: manifest.source || '',
162
169
  };
@@ -192,23 +199,72 @@ export async function syncComponentCatalog(payload, manifestInput, req) {
192
199
  }
193
200
  export async function reindexDocumentUsage(payload, sourceCollection, doc, sourceType = 'document', req) {
194
201
  const sourceDocumentId = String(doc.id);
195
- const docsForSource = await findUsageDocsForSource(payload, sourceCollection, sourceDocumentId, req);
196
- await deleteUsageDocs(payload, docsForSource, req);
197
- return createUsageDocs(payload, sourceCollection, sourceType, doc, req);
202
+ const sourceWhere = usageDocsForSourceWhere(sourceCollection, sourceDocumentId);
203
+ const sourceFields = usageSourceFields(doc);
204
+ const usageRecords = extractComponentUsageRecords(doc.nitrogenData);
205
+ // Usage rows describe tree *structure* only — nothing derived from module
206
+ // props or content. Editing copy therefore leaves every row identical, so
207
+ // diffing turns the common autosave case into zero writes instead of a full
208
+ // delete-and-recreate of every row on the save's critical path.
209
+ const existingRows = await findAllDocs(payload, NITROGEN_COMPONENT_USAGE_COLLECTION, { limit: 200, where: sourceWhere }, req);
210
+ const desiredByKey = new Map();
211
+ for (const record of usageRecords) {
212
+ desiredByKey.set(usageKeyFor(sourceCollection, sourceDocumentId, record), record);
213
+ }
214
+ const existingByKey = new Map();
215
+ const staleKeys = [];
216
+ for (const row of existingRows) {
217
+ if (desiredByKey.has(row.usageKey))
218
+ existingByKey.set(row.usageKey, row);
219
+ else
220
+ staleKeys.push(row.usageKey);
221
+ }
222
+ if (staleKeys.length > 0) {
223
+ await deleteUsageDocsWhere(payload, { usageKey: { in: staleKeys } }, req);
224
+ }
225
+ // Title/slug/status are identical across every row of the document, so when
226
+ // they change one bulk update fixes the whole set rather than N row updates.
227
+ const sourceFieldsStale = existingRows.some((row) => (row.sourceTitle || '') !== sourceFields.sourceTitle ||
228
+ (row.sourceSlug || '') !== sourceFields.sourceSlug ||
229
+ (row.sourceStatus || '') !== sourceFields.sourceStatus);
230
+ if (sourceFieldsStale && existingByKey.size > 0) {
231
+ await payload.update({
232
+ collection: NITROGEN_COMPONENT_USAGE_COLLECTION,
233
+ overrideAccess: true,
234
+ req,
235
+ where: sourceWhere,
236
+ data: sourceFields,
237
+ });
238
+ }
239
+ for (const [usageKey, record] of desiredByKey) {
240
+ const row = existingByKey.get(usageKey);
241
+ const data = usageRowData(sourceCollection, sourceType, sourceDocumentId, sourceFields, record);
242
+ if (!row) {
243
+ await payload.create({
244
+ collection: NITROGEN_COMPONENT_USAGE_COLLECTION,
245
+ overrideAccess: true,
246
+ req,
247
+ data,
248
+ });
249
+ }
250
+ else if (!usageRowIsCurrent(row, sourceType)) {
251
+ await payload.update({
252
+ collection: NITROGEN_COMPONENT_USAGE_COLLECTION,
253
+ id: row.id,
254
+ overrideAccess: true,
255
+ req,
256
+ data,
257
+ });
258
+ }
259
+ }
260
+ return usageRecords.length;
198
261
  }
199
262
  export async function deleteDocumentUsage(payload, sourceCollection, sourceDocumentId, req) {
200
- const docsForSource = await findUsageDocsForSource(payload, sourceCollection, String(sourceDocumentId), req);
201
- await deleteUsageDocs(payload, docsForSource, req);
263
+ await deleteUsageDocsWhere(payload, usageDocsForSourceWhere(sourceCollection, String(sourceDocumentId)), req);
202
264
  }
203
265
  export async function reindexAllComponentInventory(payload, collections, manifestInput, req) {
204
266
  const entries = await syncComponentCatalog(payload, manifestInput, req);
205
- const usageDocs = await findAllDocs(payload, NITROGEN_COMPONENT_USAGE_COLLECTION, {
206
- limit: 200,
207
- select: {
208
- id: true,
209
- },
210
- }, req);
211
- await deleteUsageDocs(payload, usageDocs, req);
267
+ await deleteUsageDocsWhere(payload, { id: { exists: true } }, req);
212
268
  let documentCount = 0;
213
269
  let usageCount = 0;
214
270
  for (const collection of collections) {
package/dist/types.d.ts CHANGED
@@ -152,7 +152,6 @@ export interface NitrogenComponentUsageDoc {
152
152
  id: string | number;
153
153
  usageKey: string;
154
154
  componentName: string;
155
- depth?: number;
156
155
  sourceCollection: string;
157
156
  sourceType: string;
158
157
  sourceDocumentId: string;
@@ -160,10 +159,6 @@ export interface NitrogenComponentUsageDoc {
160
159
  sourceSlug?: string;
161
160
  sourceStatus?: string;
162
161
  moduleId?: string;
163
- modulePath: string;
164
- parentComponentName?: string;
165
- parentModuleId?: string;
166
- parentModulePath?: string;
167
162
  }
168
163
  export interface NitrogenInventorySourceDoc {
169
164
  id: string | number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitrogenbuilder/connector-payload",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "Nitrogen page builder connector plugin for Payload CMS 3.x",
5
5
  "author": "Leonardo Dentzien <leo@torchmedia.ca>",
6
6
  "type": "module",
@@ -55,4 +55,4 @@
55
55
  "@nitrogenbuilder/client-core": "link:../monogen/packages/client-core",
56
56
  "@nitrogenbuilder/types": "link:../monogen/packages/types"
57
57
  }
58
- }
58
+ }