@paroicms/server
Version:
The ParoiCMS server
265 lines • 12.1 kB
JavaScript
import { createTracker, toAbsoluteUrl } from "@paroicms/internal-server-lib";
import { messageOf, parseLNodeId, unwrapReadFieldValue } from "@paroicms/public-anywhere-lib";
import { generateImageSlug, } from "@paroicms/public-server-lib";
import { isPromise } from "node:util/types";
import { getSiteFieldValue } from "../admin-backend/fields/load-fields.queries.js";
import { registerImageVariant } from "../common/medias/media-lib.js";
import { toMSourceMedia } from "../common/medias/to-media-values.js";
import { serveTextFromCacheOr } from "../common/serve-text-or.js";
import { dependencyKeyOfMedia, makeCacheDependencyKey, makePluginCacheKey, } from "../common/text-cache.js";
import { trackerReportToMessage } from "../common/tracker-report.js";
import { getPluginAssetsUrl } from "../connector/plugin-loader/plugin-public-helpers.js";
import { renderLiquidTemplate } from "../liquidjs-tools/liquidjs-rendering/render-liquidjs.js";
import { createRenderingContext, } from "../liquidjs-tools/liquidjs-rendering/rendering-context.js";
import { getHomeUrl, getUrlOfDocument } from "../rendered-site/page-route/make-url.js";
import { createDocPayloadDrop } from "../rendering-payload/create-doc-drop.js";
import { listDocValues } from "../rendering-payload/doc-list.queries.js";
import { getDocItem } from "../rendering-payload/doc-values.queries.js";
import { makeImageAvailableById } from "../rendering-payload/make-image-available.js";
import { createRunningServerConnector, createRunningSiteConnector, } from "../running-server/running-server-connector.js";
export function createBackendPluginService(siteContext, plugin) {
const service = {
...plugin.staticConfiguration,
fqdn: siteContext.fqdn,
logger: siteContext.logger,
siteSchema: siteContext.siteSchema,
siteUrl: siteContext.siteUrl,
themeConf: siteContext.themeConf,
sendMail: async (mailData) => {
await executeHook(siteContext, "sendMail", {
value: mailData,
});
},
executeHook: (hookName, payload) => executeHook(siteContext, hookName, payload),
pluginAssetsUrl: getPluginAssetsUrl(plugin),
registeredSite: siteContext.regSite,
getSiteConnector: ({ pat }) => createRunningSiteConnector({ authMethod: "pat", fqdn: siteContext.fqdn, pat }),
getUnsafeSiteConnector: ({ fqdn }) => createRunningSiteConnector({ authMethod: "none", fqdn }),
getServerConnector: createRunningServerConnector,
getSiteFieldValue: async (options) => {
const value = await getSiteFieldValue(siteContext, {
...options,
publishedOnly: true,
});
return unwrapReadFieldValue(value).value;
},
getMedia: createGetMediaFn(siteContext),
useUnversionedImage: createUseUnversionedImageFn(siteContext, plugin),
openRenderingService: async ({ language, urlLike }) => {
const tracker = createTracker();
const cacheKey = makePluginCacheKey(siteContext.logger, { pluginSlug: plugin.slug, urlLike });
const renderingContext = await createRenderingContext(siteContext, {
cacheKey,
language,
tracker,
});
return createPluginRenderingService(renderingContext, service, {
closeService: () => renderingContext.close(),
});
},
};
return service;
}
export function createPluginRenderingService(renderingContext, pluginService, options) {
return {
pluginService,
language: renderingContext.language,
homeUrl: getHomeUrl(renderingContext.siteContext, renderingContext.language),
setRenderState: (key, value) => {
renderingContext.pluginRenderState.set(key, value);
},
loadDocuments: (async (loadDescriptor, options) => {
if (options?.withTotal) {
const { items, total } = await listDocValues(renderingContext, loadDescriptor, {
onlyPublished: true,
withTotal: true,
});
const documents = items.map((item) => createDocPayloadDrop(renderingContext, item, { documentId: item }));
return { documents, total };
}
const { items } = await listDocValues(renderingContext, loadDescriptor, {
onlyPublished: true,
withTotal: false,
});
return items.map((item) => createDocPayloadDrop(renderingContext, item, { documentId: item }));
}),
renderDocument: async (templateName, doc) => {
return await renderLiquidTemplate(renderingContext, {
payload: { doc },
templateNames: [normalizeTemplateName(templateName)],
});
},
serve: async (httpContext, response) => {
const { siteContext, tracker } = renderingContext;
const serveOr = serveTextFromCacheOr(siteContext, httpContext, tracker, {
cacheKey: renderingContext.cacheKey,
contentType: response.contentType,
});
const dependencyKeys = renderingContext.getDependencyKeys();
await serveOr.putInCacheAndServe(response.content, dependencyKeys);
siteContext.logger.stats(trackerReportToMessage(tracker.toReport(), `serve ${httpContext.req.relativeUrl}`));
},
useImage: createUseImageFn(renderingContext),
getDocument: async (documentId) => {
return await getDocument(renderingContext, documentId);
},
close: options?.closeService ??
(async () => {
}),
};
}
function createGetMediaFn(siteContext) {
return async (sel, options = {}) => {
const { absoluteUrl, withAttachedData } = options;
let media;
if ("mediaId" in sel) {
const { mediaId } = sel;
media = await siteContext.mediaStorage.getMedia({ mediaId, withAttachedData });
}
else {
const { handle } = sel;
media = await siteContext.mediaStorage.getMedia({ handle, withAttachedData });
}
if (!media)
return;
return toMSourceMedia(siteContext, media, { absoluteUrl });
};
}
function createUseUnversionedImageFn(siteContext, plugin) {
return async (image, resizeRule, { pixelRatio, absoluteUrl } = {}) => {
if (!siteContext.themeConf.fTextImageResizeRules.includes(resizeRule)) {
throw new Error(`Invalid unversioned resize rule "${resizeRule}" for plugin "${plugin.slug}"`);
}
const mediaId = typeof image === "string" ? image : image.mediaId;
let source = typeof image === "string" ? undefined : image;
if (!source) {
const media = await siteContext.mediaStorage.getMedia({
mediaId,
});
if (media?.kind !== "image")
return;
source = toMSourceMedia(siteContext, media, { absoluteUrl: true });
}
if (!source)
return;
const variant = await registerImageVariant(siteContext, {
autoCrop: false,
sourceImage: {
mediaId: source.mediaId,
mediaType: source.mediaType,
rawWidth: source.rawWidth,
rawHeight: source.rawHeight,
slug: generateImageSlug(source.originalName),
},
resizeRule,
pixelRatio: pixelRatio ?? source.pixelRatio ?? siteContext.themeConf.pixelRatio,
absoluteUrl: absoluteUrl ?? false,
versionedUrl: false,
ownerHandle: `plugin:${plugin.slug}:unversioned`,
isHandleReusable: true,
canBeRetrievedByOwner: false,
originalName: source.originalName,
mediaType: source.mediaType,
});
return variant;
};
}
function createUseImageFn(renderingContext) {
return async (image, resizeRule, { pixelRatio, absoluteUrl } = {}) => {
const mediaId = typeof image === "string" ? image : image.mediaId || image.mediaId;
renderingContext.addDependencyKey(dependencyKeyOfMedia(mediaId));
try {
return await makeImageAvailableById(renderingContext, { mediaId: typeof image === "string" ? image : image.mediaId }, { resizeRule, pixelRatio, absoluteUrl });
}
catch (err) {
renderingContext.siteContext.logger.debug(`Error while making image available: ${messageOf(err)}`);
return;
}
};
}
async function getDocument(renderingContext, rawDocumentId) {
const { siteContext } = renderingContext;
const documentId = parseLNodeId(rawDocumentId);
renderingContext.addDependencyKey(makeCacheDependencyKey({ documentId }));
const docPayload = await getDocItem(siteContext, renderingContext.tracker, documentId, {
allowUndef: true,
});
if (!docPayload)
return;
return {
id: rawDocumentId,
getUrl: async (options) => {
const { url, dependencyKeys } = await getUrlOfDocument(siteContext, renderingContext.tracker, documentId, { returnUrlDependencyKeys: true });
renderingContext.addDependencyKey(...dependencyKeys);
return options?.absoluteUrl ? toAbsoluteUrl(siteContext, url) : url;
},
typeName: docPayload.type,
nodeId: docPayload.nodeId,
language: docPayload.language,
relativeId: docPayload.relativeId,
title: docPayload.title,
slug: docPayload.slug,
publishDate: docPayload.publishDate,
};
}
export function executeHook(siteContext, hookName, hookPayload = {}) {
const list = siteContext.hooks.get(hookName);
if (!list)
return hookPayload.value;
let value = hookPayload.value;
for (let i = 0; i < list.length; ++i) {
const { handler, plugin } = list[i];
if (hookPayload.pluginName && plugin.slug !== hookPayload.pluginName)
continue;
const service = createBackendPluginService(siteContext, plugin);
try {
const result = handler({
service,
options: hookPayload.options,
value,
});
if (isPromise(result)) {
return executeRemainingHooksAsync(siteContext, hookName, hookPayload, list, i, result);
}
value = result;
}
catch (error) {
siteContext.logger.error(`Error while executing hook "${hookName}" for plugin "${plugin.slug}": ${messageOf(error)}`);
}
}
return value;
}
async function executeRemainingHooksAsync(siteContext, hookName, hookPayload, list, currentIndex, firstAsyncResult) {
let value = await firstAsyncResult;
for (let j = currentIndex + 1; j < list.length; ++j) {
const { handler: nextHandler, plugin: nextPlugin } = list[j];
if (hookPayload.pluginName && nextPlugin.slug !== hookPayload.pluginName)
continue;
const nextService = createBackendPluginService(siteContext, nextPlugin);
try {
const nextResult = nextHandler({
service: nextService,
options: hookPayload.options,
value,
});
if (isPromise(nextResult)) {
value = await nextResult;
}
else {
value = nextResult;
}
}
catch (error) {
siteContext.logger.error(`Error while executing hook "${hookName}" for plugin "${nextPlugin.slug}": ${messageOf(error)}`);
}
}
return value;
}
function normalizeTemplateName(templateName) {
if (templateName.endsWith(".public"))
return `${templateName}.liquid`;
if (templateName.endsWith(".public.liquid"))
return templateName;
throw new Error(`Must be a ".public" template name: "${templateName}"`);
}
//# sourceMappingURL=make-backend-plugin-service.js.map