@nitrogenbuilder/connector-payload 0.1.34 → 0.1.36

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 (38) hide show
  1. package/dist/collections/NitrogenComponentCatalog.d.ts +2 -0
  2. package/dist/collections/NitrogenComponentCatalog.js +44 -0
  3. package/dist/collections/NitrogenComponentUsage.d.ts +2 -0
  4. package/dist/collections/NitrogenComponentUsage.js +81 -0
  5. package/dist/components/LockedJsonField.d.ts +2 -9
  6. package/dist/components/LockedJsonField.js +7 -6
  7. package/dist/components/LockedJsonFieldRuntime.d.ts +3 -7
  8. package/dist/components/LockedJsonFieldRuntime.js +21 -22
  9. package/dist/components/NitrogenComponentInventoryClient.d.ts +13 -0
  10. package/dist/components/NitrogenComponentInventoryClient.js +309 -0
  11. package/dist/components/NitrogenComponentInventoryField.d.ts +3 -0
  12. package/dist/components/NitrogenComponentInventoryField.js +26 -0
  13. package/dist/components/NitrogenComponentInventoryNavLink.d.ts +1 -0
  14. package/dist/components/NitrogenComponentInventoryNavLink.js +75 -0
  15. package/dist/components/NitrogenComponentInventoryReindexButton.d.ts +1 -0
  16. package/dist/components/NitrogenComponentInventoryReindexButton.js +91 -0
  17. package/dist/components/NitrogenComponentInventoryView.d.ts +2 -0
  18. package/dist/components/NitrogenComponentInventoryView.js +28 -0
  19. package/dist/components/NitrogenDataViewer.d.ts +2 -2
  20. package/dist/components/NitrogenDataViewer.js +7 -6
  21. package/dist/components/NitrogenDataViewerRuntime.d.ts +7 -2
  22. package/dist/components/NitrogenDataViewerRuntime.js +171 -93
  23. package/dist/components/NitrogenNavGroup.d.ts +1 -0
  24. package/dist/components/NitrogenNavGroup.js +74 -0
  25. package/dist/editor/NitrogenEditorPage.d.ts +1 -2
  26. package/dist/endpoints/component-inventory.d.ts +6 -0
  27. package/dist/endpoints/component-inventory.js +20 -0
  28. package/dist/frontend/NitrogenWrapper.d.ts +1 -2
  29. package/dist/globals/NitrogenComponents.d.ts +2 -0
  30. package/dist/globals/NitrogenComponents.js +22 -0
  31. package/dist/index.d.ts +12 -2
  32. package/dist/index.js +94 -3
  33. package/dist/inventory/indexing.d.ts +31 -0
  34. package/dist/inventory/indexing.js +230 -0
  35. package/dist/inventory/manifest.d.ts +3 -0
  36. package/dist/inventory/manifest.js +22 -0
  37. package/dist/types.d.ts +35 -1
  38. package/package.json +7 -2
@@ -0,0 +1,74 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { usePathname } from 'next/navigation';
4
+ import { useEffect, useMemo, useState } from 'react';
5
+ const items = [
6
+ {
7
+ href: '/admin/collections/nitrogen-templates',
8
+ label: 'Templates',
9
+ },
10
+ {
11
+ href: '/admin/globals/nitrogen-settings',
12
+ label: 'Settings',
13
+ },
14
+ {
15
+ href: '/admin/nitrogen/components',
16
+ label: 'Components',
17
+ },
18
+ ];
19
+ export default function NitrogenNavGroup() {
20
+ const pathname = usePathname();
21
+ const hasActiveChild = useMemo(() => items.some((item) => pathname === item.href || pathname?.startsWith(`${item.href}/`)), [pathname]);
22
+ const [collapsed, setCollapsed] = useState(!hasActiveChild);
23
+ useEffect(() => {
24
+ if (hasActiveChild) {
25
+ setCollapsed(false);
26
+ }
27
+ }, [hasActiveChild]);
28
+ useEffect(() => {
29
+ const styleId = 'nitrogen-custom-nav-group-style';
30
+ if (document.getElementById(styleId)) {
31
+ return;
32
+ }
33
+ const style = document.createElement('style');
34
+ style.id = styleId;
35
+ style.textContent = `
36
+ #nav-group-Nitrogen {
37
+ display: none;
38
+ }
39
+ `;
40
+ document.head.appendChild(style);
41
+ return () => {
42
+ style.remove();
43
+ };
44
+ }, []);
45
+ return (_jsxs("div", { style: { marginTop: 12 }, children: [_jsxs("button", { type: "button", onClick: () => setCollapsed((current) => !current), style: {
46
+ alignItems: 'center',
47
+ background: 'transparent',
48
+ border: 'none',
49
+ color: 'inherit',
50
+ cursor: 'pointer',
51
+ display: 'flex',
52
+ font: 'inherit',
53
+ justifyContent: 'space-between',
54
+ padding: '6px 0',
55
+ width: '100%',
56
+ }, children: [_jsx("span", { style: { fontSize: 13, fontWeight: 700, letterSpacing: '0.01em' }, children: "Nitrogen" }), _jsx("span", { "aria-hidden": "true", style: {
57
+ display: 'inline-block',
58
+ fontSize: 12,
59
+ opacity: 0.65,
60
+ transform: collapsed ? 'rotate(-90deg)' : 'rotate(0deg)',
61
+ transition: 'transform 160ms ease',
62
+ }, children: "\u25BE" })] }), !collapsed && (_jsx("div", { style: { display: 'grid', gap: 2, paddingBottom: 4, paddingLeft: 12 }, children: items.map((item) => {
63
+ const isActive = pathname === item.href || pathname?.startsWith(`${item.href}/`);
64
+ return (_jsx("a", { "aria-current": isActive ? 'page' : undefined, href: item.href, style: {
65
+ borderLeft: isActive ? '2px solid currentColor' : '2px solid transparent',
66
+ display: 'block',
67
+ fontSize: 13,
68
+ fontWeight: isActive ? 700 : 500,
69
+ opacity: isActive ? 1 : 0.82,
70
+ padding: '6px 0 6px 10px',
71
+ textDecoration: 'none',
72
+ }, children: item.label }, item.href));
73
+ }) }))] }));
74
+ }
@@ -1,4 +1,3 @@
1
- import type { SanitizedConfig } from "payload";
2
1
  interface NitrogenEditorSearchParams {
3
2
  pageId?: string;
4
3
  collection?: string;
@@ -16,7 +15,7 @@ interface NitrogenEditorSearchParams {
16
15
  *
17
16
  * Accessible at `/nitrogen-editor?pageId=xxx`.
18
17
  */
19
- export declare function createNitrogenEditorPage(config: Promise<SanitizedConfig>): ({ searchParams, }: {
18
+ export declare function createNitrogenEditorPage(config: Promise<any>): ({ searchParams, }: {
20
19
  searchParams: Promise<NitrogenEditorSearchParams>;
21
20
  }) => Promise<import("react/jsx-runtime").JSX.Element>;
22
21
  export {};
@@ -0,0 +1,6 @@
1
+ import type { Endpoint } from 'payload';
2
+ import type { NitrogenComponentManifestInput } from '../inventory/manifest';
3
+ export declare function createComponentInventoryEndpoints(args: {
4
+ collections: string[];
5
+ componentManifest?: NitrogenComponentManifestInput;
6
+ }): Endpoint[];
@@ -0,0 +1,20 @@
1
+ import { requireAuth } from './helpers';
2
+ import { reindexAllComponentInventory } from '../inventory/indexing';
3
+ export function createComponentInventoryEndpoints(args) {
4
+ return [
5
+ {
6
+ path: '/nitrogen/v1/component-inventory/reindex',
7
+ method: 'post',
8
+ handler: async (req) => {
9
+ const authError = requireAuth(req);
10
+ if (authError)
11
+ return authError;
12
+ const result = await reindexAllComponentInventory(req.payload, args.collections, args.componentManifest);
13
+ return Response.json({
14
+ success: true,
15
+ ...result,
16
+ });
17
+ },
18
+ },
19
+ ];
20
+ }
@@ -1,5 +1,4 @@
1
1
  import React from 'react';
2
- import type { SanitizedConfig } from 'payload';
3
2
  import type { BuilderModule } from '@nitrogenbuilder/types';
4
3
  export interface NitrogenWrapperProps {
5
4
  /**
@@ -21,7 +20,7 @@ export interface NitrogenWrapperProps {
21
20
  /**
22
21
  * The Payload config promise. Import from '@payload-config' in your app and pass it here.
23
22
  */
24
- config: Promise<SanitizedConfig>;
23
+ config: Promise<any>;
25
24
  /**
26
25
  * Regular page content to render when not in Nitrogen mode.
27
26
  */
@@ -0,0 +1,2 @@
1
+ import type { GlobalConfig } from 'payload';
2
+ export declare const NitrogenComponents: GlobalConfig;
@@ -0,0 +1,22 @@
1
+ export const NitrogenComponents = {
2
+ slug: 'nitrogen-components',
3
+ label: 'Nitrogen Components',
4
+ admin: {
5
+ group: 'Nitrogen',
6
+ },
7
+ access: {
8
+ read: ({ req }) => !!req.user,
9
+ update: ({ req }) => !!req.user,
10
+ },
11
+ fields: [
12
+ {
13
+ name: 'inventory',
14
+ type: 'ui',
15
+ admin: {
16
+ components: {
17
+ Field: '@nitrogenbuilder/connector-payload/components/NitrogenComponentInventoryField',
18
+ },
19
+ },
20
+ },
21
+ ],
22
+ };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Plugin } from "payload";
1
+ import type { NitrogenComponentManifestInput } from './inventory/manifest';
2
2
  export interface NitrogenConnectorPluginOptions {
3
3
  /** Disable the plugin without removing it from config */
4
4
  disabled?: boolean;
@@ -18,8 +18,18 @@ export interface NitrogenConnectorPluginOptions {
18
18
  * `collectionRoutes: { posts: '/blog/[slug]', patent: '/patents/[slug]' }`
19
19
  */
20
20
  collectionRoutes?: Record<string, string>;
21
+ /**
22
+ * A static manifest or async loader describing the project's registered
23
+ * Nitrogen components and their prop schemas.
24
+ */
25
+ componentManifest?: NitrogenComponentManifestInput;
26
+ /**
27
+ * Collections whose saved `nitrogenData` should be indexed for the component
28
+ * inventory. These do not need to be editor-enabled.
29
+ */
30
+ indexCollections?: string[];
21
31
  }
22
- export declare const nitrogenConnectorPlugin: (options?: NitrogenConnectorPluginOptions) => Plugin;
32
+ export declare const nitrogenConnectorPlugin: (options?: NitrogenConnectorPluginOptions) => any;
23
33
  export { NitrogenTemplates } from "./collections/NitrogenTemplates";
24
34
  export { NitrogenSettings } from "./globals/NitrogenSettings";
25
35
  export { NitrogenEditButton } from "./components/NitrogenEditButton";
package/dist/index.js CHANGED
@@ -1,5 +1,8 @@
1
1
  import { NitrogenTemplates } from "./collections/NitrogenTemplates";
2
+ import { NitrogenComponentCatalog } from './collections/NitrogenComponentCatalog';
3
+ import { NitrogenComponentUsage } from './collections/NitrogenComponentUsage';
2
4
  import { NitrogenSettings } from "./globals/NitrogenSettings";
5
+ import { NitrogenComponents } from './globals/NitrogenComponents';
3
6
  import { templatesEndpoints } from "./endpoints/templates";
4
7
  import { mediaEndpoints } from "./endpoints/media";
5
8
  import { nitrogenSettingsEndpoints } from "./endpoints/nitrogen-settings";
@@ -7,8 +10,10 @@ import { allEndpoints } from "./endpoints/all";
7
10
  import { collectionsEndpoints } from "./endpoints/collections";
8
11
  import { menuEndpoints } from "./endpoints/menu";
9
12
  import { createCollectionEndpoints } from "./endpoints/collection-endpoints";
13
+ import { createComponentInventoryEndpoints } from './endpoints/component-inventory';
10
14
  import { batchEndpoints } from "./endpoints/batch";
11
15
  import { registerCollection } from "./collection-registry";
16
+ import { deleteDocumentUsage, reindexDocumentUsage, syncComponentCatalog, } from './inventory/indexing';
12
17
  /** Fields required by Nitrogen that will be injected into collections if missing */
13
18
  const nitrogenRequiredFields = [
14
19
  {
@@ -37,10 +42,48 @@ export const nitrogenConnectorPlugin = (options = {}) => (incomingConfig) => {
37
42
  return incomingConfig;
38
43
  }
39
44
  const config = { ...incomingConfig };
45
+ const inventoryCollections = Array.from(new Set([
46
+ ...(options.collections || []),
47
+ ...(options.indexCollections || []),
48
+ 'nitrogen-templates',
49
+ ]));
50
+ const createAfterChangeHook = (collectionSlug) => {
51
+ return async ({ doc, req }) => {
52
+ if (doc) {
53
+ await reindexDocumentUsage(req.payload, collectionSlug, doc, collectionSlug === 'nitrogen-templates' ? 'template' : 'document');
54
+ }
55
+ return doc;
56
+ };
57
+ };
58
+ const createAfterDeleteHook = (collectionSlug) => {
59
+ return async ({ doc, id, req, }) => {
60
+ const sourceId = doc?.id ?? id;
61
+ if (sourceId !== undefined) {
62
+ await deleteDocumentUsage(req.payload, collectionSlug, sourceId);
63
+ }
64
+ return doc;
65
+ };
66
+ };
67
+ const withInventoryHooks = (collection, collectionSlug) => {
68
+ const hooks = collection.hooks || {};
69
+ return {
70
+ ...collection,
71
+ hooks: {
72
+ ...hooks,
73
+ afterChange: [...(hooks.afterChange || []), createAfterChangeHook(collectionSlug)],
74
+ afterDelete: [...(hooks.afterDelete || []), createAfterDeleteHook(collectionSlug)],
75
+ },
76
+ };
77
+ };
40
78
  // Add Nitrogen templates collection
41
- config.collections = [...(config.collections || []), NitrogenTemplates];
42
- // Add Nitrogen global settings
43
- config.globals = [...(config.globals || []), NitrogenSettings];
79
+ config.collections = [
80
+ ...(config.collections || []),
81
+ withInventoryHooks(NitrogenTemplates, 'nitrogen-templates'),
82
+ NitrogenComponentCatalog,
83
+ NitrogenComponentUsage,
84
+ ];
85
+ // Add Nitrogen globals
86
+ config.globals = [...(config.globals || []), NitrogenSettings, NitrogenComponents];
44
87
  // Add Nitrogen API endpoints
45
88
  config.endpoints = [
46
89
  ...(config.endpoints || []),
@@ -51,7 +94,45 @@ export const nitrogenConnectorPlugin = (options = {}) => (incomingConfig) => {
51
94
  ...collectionsEndpoints,
52
95
  ...menuEndpoints,
53
96
  ...batchEndpoints,
97
+ ...createComponentInventoryEndpoints({
98
+ collections: inventoryCollections,
99
+ componentManifest: options.componentManifest,
100
+ }),
54
101
  ];
102
+ const existingOnInit = config.onInit;
103
+ config.onInit = async (payload) => {
104
+ if (existingOnInit) {
105
+ await existingOnInit(payload);
106
+ }
107
+ try {
108
+ await syncComponentCatalog(payload, options.componentManifest);
109
+ }
110
+ catch (error) {
111
+ payload.logger.error(`[@nitrogenbuilder/connector-payload] Failed to sync component catalog: ${error instanceof Error ? error.message : String(error)}`);
112
+ }
113
+ for (const slug of ['nitrogen-components']) {
114
+ try {
115
+ await payload.findGlobal({
116
+ slug,
117
+ depth: 0,
118
+ overrideAccess: true,
119
+ });
120
+ }
121
+ catch {
122
+ try {
123
+ await payload.updateGlobal({
124
+ slug,
125
+ data: {},
126
+ depth: 0,
127
+ overrideAccess: true,
128
+ });
129
+ }
130
+ catch (error) {
131
+ payload.logger.warn(`[@nitrogenbuilder/connector-payload] Could not initialize global "${slug}": ${error instanceof Error ? error.message : String(error)}`);
132
+ }
133
+ }
134
+ }
135
+ };
55
136
  // Register templates collection
56
137
  registerCollection("nitrogen-templates", "nitrogen-templates", "/nitrogen-templates/[slug]");
57
138
  registerCollection("nitrogen_template", "nitrogen-templates", "/nitrogen-templates/[slug]");
@@ -133,6 +214,16 @@ export const nitrogenConnectorPlugin = (options = {}) => (incomingConfig) => {
133
214
  // Register in the collection registry for the `all` endpoint
134
215
  registerCollection(slug, slug, routePattern);
135
216
  }
217
+ for (const slug of inventoryCollections) {
218
+ if (slug === 'nitrogen-templates')
219
+ continue;
220
+ const existingIndex = (config.collections || []).findIndex((collection) => collection.slug === slug);
221
+ if (existingIndex === -1) {
222
+ console.warn(`[@nitrogenbuilder/connector-payload] Index collection "${slug}" not found in config.`);
223
+ continue;
224
+ }
225
+ config.collections[existingIndex] = withInventoryHooks(config.collections[existingIndex], slug);
226
+ }
136
227
  return config;
137
228
  };
138
229
  // Re-export for consumers who need direct access
@@ -0,0 +1,31 @@
1
+ import type { ComponentManifestEntry } from '@nitrogenbuilder/types';
2
+ import type { Payload, Where } from 'payload';
3
+ import type { NitrogenInventorySourceDoc } from '../types';
4
+ import { type NitrogenComponentManifestInput } from './manifest';
5
+ export declare const NITROGEN_COMPONENT_CATALOG_COLLECTION = "nitrogen-component-catalog";
6
+ export declare const NITROGEN_COMPONENT_USAGE_COLLECTION = "nitrogen-component-usage";
7
+ type ComponentUsageRecord = {
8
+ componentName: string;
9
+ depth: number;
10
+ moduleId: string;
11
+ modulePath: string;
12
+ parentComponentName?: string;
13
+ parentModuleId?: string;
14
+ parentModulePath?: string;
15
+ };
16
+ type ReindexResult = {
17
+ catalogCount: number;
18
+ documentCount: number;
19
+ usageCount: number;
20
+ };
21
+ export declare function extractComponentUsageRecords(modules: unknown): ComponentUsageRecord[];
22
+ export declare function findAllDocs<T extends object>(payload: Payload, collection: string, options?: {
23
+ limit?: number;
24
+ select?: Record<string, true>;
25
+ where?: Where;
26
+ }): Promise<T[]>;
27
+ export declare function syncComponentCatalog(payload: Payload, manifestInput?: NitrogenComponentManifestInput): Promise<ComponentManifestEntry[]>;
28
+ export declare function reindexDocumentUsage(payload: Payload, sourceCollection: string, doc: NitrogenInventorySourceDoc, sourceType?: 'document' | 'template'): Promise<number>;
29
+ export declare function deleteDocumentUsage(payload: Payload, sourceCollection: string, sourceDocumentId: string | number): Promise<void>;
30
+ export declare function reindexAllComponentInventory(payload: Payload, collections: string[], manifestInput?: NitrogenComponentManifestInput): Promise<ReindexResult>;
31
+ export {};
@@ -0,0 +1,230 @@
1
+ import { resolveComponentManifest, } from './manifest';
2
+ export const NITROGEN_COMPONENT_CATALOG_COLLECTION = 'nitrogen-component-catalog';
3
+ export const NITROGEN_COMPONENT_USAGE_COLLECTION = 'nitrogen-component-usage';
4
+ function moduleNameFromNode(node) {
5
+ const value = node.module;
6
+ if (typeof value === 'string')
7
+ return value;
8
+ if (value && typeof value === 'object' && typeof value.name === 'string') {
9
+ return String(value.name);
10
+ }
11
+ return null;
12
+ }
13
+ function collectUsageFromTree(modules, pathPrefix, results, parentContext, depth = 0) {
14
+ if (!Array.isArray(modules))
15
+ return;
16
+ modules.forEach((maybeNode, index) => {
17
+ if (!maybeNode || typeof maybeNode !== 'object')
18
+ return;
19
+ const node = maybeNode;
20
+ const modulePath = `${pathPrefix}[${index}]`;
21
+ const componentName = moduleNameFromNode(node);
22
+ const moduleId = typeof node.id === 'string' || typeof node.id === 'number'
23
+ ? String(node.id)
24
+ : modulePath;
25
+ const currentContext = componentName
26
+ ? {
27
+ componentName,
28
+ moduleId,
29
+ modulePath,
30
+ }
31
+ : parentContext;
32
+ if (componentName) {
33
+ results.push({
34
+ componentName,
35
+ depth,
36
+ moduleId,
37
+ modulePath,
38
+ parentComponentName: parentContext?.componentName,
39
+ parentModuleId: parentContext?.moduleId,
40
+ parentModulePath: parentContext?.modulePath,
41
+ });
42
+ }
43
+ const props = node.props;
44
+ if (props && typeof props === 'object') {
45
+ const children = props.children;
46
+ if (Array.isArray(children)) {
47
+ collectUsageFromTree(children, `${modulePath}.props.children`, results, currentContext, depth + 1);
48
+ }
49
+ else if (children && typeof children === 'object') {
50
+ for (const [slotName, slotChildren] of Object.entries(children)) {
51
+ collectUsageFromTree(slotChildren, `${modulePath}.props.children.${slotName}`, results, currentContext, depth + 1);
52
+ }
53
+ }
54
+ }
55
+ if (Array.isArray(node.children)) {
56
+ collectUsageFromTree(node.children, `${modulePath}.children`, results, currentContext, depth + 1);
57
+ }
58
+ });
59
+ }
60
+ export function extractComponentUsageRecords(modules) {
61
+ const results = [];
62
+ collectUsageFromTree(modules, 'modules', results);
63
+ return results;
64
+ }
65
+ export async function findAllDocs(payload, collection, options) {
66
+ const docs = [];
67
+ let page = 1;
68
+ let hasNextPage = true;
69
+ while (hasNextPage) {
70
+ const result = await payload.find({
71
+ collection: collection,
72
+ depth: 0,
73
+ limit: options?.limit ?? 200,
74
+ page,
75
+ overrideAccess: true,
76
+ pagination: true,
77
+ select: options?.select,
78
+ where: options?.where,
79
+ });
80
+ docs.push(...result.docs);
81
+ hasNextPage = Boolean(result.hasNextPage);
82
+ page += 1;
83
+ }
84
+ return docs;
85
+ }
86
+ async function deleteUsageDocs(payload, docs) {
87
+ for (const doc of docs) {
88
+ await payload.delete({
89
+ collection: NITROGEN_COMPONENT_USAGE_COLLECTION,
90
+ id: doc.id,
91
+ overrideAccess: true,
92
+ });
93
+ }
94
+ }
95
+ async function findUsageDocsForSource(payload, sourceCollection, sourceDocumentId) {
96
+ return findAllDocs(payload, NITROGEN_COMPONENT_USAGE_COLLECTION, {
97
+ limit: 100,
98
+ select: {
99
+ id: true,
100
+ },
101
+ where: {
102
+ and: [
103
+ {
104
+ sourceCollection: {
105
+ equals: sourceCollection,
106
+ },
107
+ },
108
+ {
109
+ sourceDocumentId: {
110
+ equals: sourceDocumentId,
111
+ },
112
+ },
113
+ ],
114
+ },
115
+ });
116
+ }
117
+ async function createUsageDocs(payload, sourceCollection, sourceType, doc) {
118
+ const sourceDocumentId = String(doc.id);
119
+ const usageRecords = extractComponentUsageRecords(doc.nitrogenData);
120
+ for (const record of usageRecords) {
121
+ await payload.create({
122
+ collection: NITROGEN_COMPONENT_USAGE_COLLECTION,
123
+ overrideAccess: true,
124
+ data: {
125
+ usageKey: `${sourceCollection}:${sourceDocumentId}:${record.moduleId}:${record.modulePath}:${record.componentName}`,
126
+ componentName: record.componentName,
127
+ depth: record.depth,
128
+ sourceCollection,
129
+ sourceType,
130
+ sourceDocumentId,
131
+ sourceTitle: doc.title || '',
132
+ sourceSlug: doc.slug || '',
133
+ sourceStatus: doc.status || doc._status || '',
134
+ moduleId: record.moduleId,
135
+ modulePath: record.modulePath,
136
+ parentComponentName: record.parentComponentName || '',
137
+ parentModuleId: record.parentModuleId || '',
138
+ parentModulePath: record.parentModulePath || '',
139
+ },
140
+ });
141
+ }
142
+ return usageRecords.length;
143
+ }
144
+ export async function syncComponentCatalog(payload, manifestInput) {
145
+ const manifest = await resolveComponentManifest(manifestInput);
146
+ const entries = manifest.components || [];
147
+ const existingDocs = await findAllDocs(payload, NITROGEN_COMPONENT_CATALOG_COLLECTION);
148
+ const existingByName = new Map(existingDocs.map((doc) => [doc.componentName, doc]));
149
+ const nextNames = new Set(entries.map((entry) => entry.name));
150
+ for (const entry of entries) {
151
+ const existing = existingByName.get(entry.name);
152
+ const data = {
153
+ componentName: entry.name,
154
+ scope: entry.scope || '',
155
+ sidebarCategory: entry.sidebarCategory || '',
156
+ icon: entry.icon || '',
157
+ definition: entry,
158
+ source: manifest.source || '',
159
+ };
160
+ if (existing) {
161
+ await payload.update({
162
+ collection: NITROGEN_COMPONENT_CATALOG_COLLECTION,
163
+ id: existing.id,
164
+ data,
165
+ overrideAccess: true,
166
+ });
167
+ }
168
+ else {
169
+ await payload.create({
170
+ collection: NITROGEN_COMPONENT_CATALOG_COLLECTION,
171
+ data,
172
+ overrideAccess: true,
173
+ });
174
+ }
175
+ }
176
+ for (const existing of existingDocs) {
177
+ if (!nextNames.has(existing.componentName)) {
178
+ await payload.delete({
179
+ collection: NITROGEN_COMPONENT_CATALOG_COLLECTION,
180
+ id: existing.id,
181
+ overrideAccess: true,
182
+ });
183
+ }
184
+ }
185
+ return entries;
186
+ }
187
+ export async function reindexDocumentUsage(payload, sourceCollection, doc, sourceType = 'document') {
188
+ const sourceDocumentId = String(doc.id);
189
+ const docsForSource = await findUsageDocsForSource(payload, sourceCollection, sourceDocumentId);
190
+ await deleteUsageDocs(payload, docsForSource);
191
+ return createUsageDocs(payload, sourceCollection, sourceType, doc);
192
+ }
193
+ export async function deleteDocumentUsage(payload, sourceCollection, sourceDocumentId) {
194
+ const docsForSource = await findUsageDocsForSource(payload, sourceCollection, String(sourceDocumentId));
195
+ await deleteUsageDocs(payload, docsForSource);
196
+ }
197
+ export async function reindexAllComponentInventory(payload, collections, manifestInput) {
198
+ const entries = await syncComponentCatalog(payload, manifestInput);
199
+ const usageDocs = await findAllDocs(payload, NITROGEN_COMPONENT_USAGE_COLLECTION, {
200
+ limit: 200,
201
+ select: {
202
+ id: true,
203
+ },
204
+ });
205
+ await deleteUsageDocs(payload, usageDocs);
206
+ let documentCount = 0;
207
+ let usageCount = 0;
208
+ for (const collection of collections) {
209
+ const docs = await findAllDocs(payload, collection, {
210
+ limit: 100,
211
+ select: {
212
+ id: true,
213
+ title: true,
214
+ slug: true,
215
+ status: true,
216
+ _status: true,
217
+ nitrogenData: true,
218
+ },
219
+ });
220
+ for (const doc of docs) {
221
+ documentCount += 1;
222
+ usageCount += await createUsageDocs(payload, collection, collection === 'nitrogen-templates' ? 'template' : 'document', doc);
223
+ }
224
+ }
225
+ return {
226
+ catalogCount: entries.length,
227
+ documentCount,
228
+ usageCount,
229
+ };
230
+ }
@@ -0,0 +1,3 @@
1
+ import type { ComponentManifest, ComponentManifestEntry } from '@nitrogenbuilder/types';
2
+ export type NitrogenComponentManifestInput = ComponentManifest | ComponentManifestEntry[] | (() => Promise<ComponentManifest | ComponentManifestEntry[]> | ComponentManifest | ComponentManifestEntry[]);
3
+ export declare function resolveComponentManifest(input?: NitrogenComponentManifestInput): Promise<ComponentManifest>;
@@ -0,0 +1,22 @@
1
+ export async function resolveComponentManifest(input) {
2
+ if (!input) {
3
+ return {
4
+ components: [],
5
+ generatedAt: new Date().toISOString(),
6
+ source: 'empty',
7
+ };
8
+ }
9
+ const resolved = typeof input === 'function' ? await input() : input;
10
+ if (Array.isArray(resolved)) {
11
+ return {
12
+ components: resolved,
13
+ generatedAt: new Date().toISOString(),
14
+ source: 'array',
15
+ };
16
+ }
17
+ return {
18
+ components: resolved.components || [],
19
+ generatedAt: resolved.generatedAt || new Date().toISOString(),
20
+ source: resolved.source,
21
+ };
22
+ }
package/dist/types.d.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * Document interfaces matching the Payload collection/global schemas,
5
5
  * plus request body shapes used by the API endpoints.
6
6
  */
7
- import type { BuilderModule } from '@nitrogenbuilder/types';
7
+ import type { BuilderModule, ComponentManifestEntry } from '@nitrogenbuilder/types';
8
8
  export type JsonObject = Record<string, unknown>;
9
9
  export type NitrogenModule = BuilderModule;
10
10
  interface NitrogenDocBase {
@@ -128,4 +128,38 @@ export interface RenderDataRequestBody {
128
128
  /** Which Payload collection to fetch dynamic data from */
129
129
  collection?: string;
130
130
  }
131
+ export interface NitrogenComponentCatalogDoc {
132
+ id: string | number;
133
+ componentName: string;
134
+ scope?: string;
135
+ sidebarCategory?: string;
136
+ icon?: string;
137
+ definition?: ComponentManifestEntry | JsonObject;
138
+ source?: string;
139
+ }
140
+ export interface NitrogenComponentUsageDoc {
141
+ id: string | number;
142
+ usageKey: string;
143
+ componentName: string;
144
+ depth?: number;
145
+ sourceCollection: string;
146
+ sourceType: string;
147
+ sourceDocumentId: string;
148
+ sourceTitle?: string;
149
+ sourceSlug?: string;
150
+ sourceStatus?: string;
151
+ moduleId?: string;
152
+ modulePath: string;
153
+ parentComponentName?: string;
154
+ parentModuleId?: string;
155
+ parentModulePath?: string;
156
+ }
157
+ export interface NitrogenInventorySourceDoc {
158
+ id: string | number;
159
+ title?: string;
160
+ slug?: string;
161
+ status?: string;
162
+ _status?: string;
163
+ nitrogenData?: JsonObject | JsonObject[];
164
+ }
131
165
  export {};