UNPKG

@paroicms/server

Version:
265 lines 12.1 kB
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