UNPKG

@paroicms/server

Version:
298 lines 12.2 kB
import { ensureDirectory } from "@paroicms/internal-server-lib"; import { isObj } from "@paroicms/public-anywhere-lib"; import { ApiError, loadSimpleTranslatorFromDirectory, } from "@paroicms/public-server-lib"; import { createImageCacheEngine } from "@paroicms/server-image-cache-engine"; import { createNoTextCacheSystem, createTextCacheSystem, } from "@paroicms/server-text-cache-system"; import { Liquid } from "liquidjs"; import { join } from "node:path"; import { getScheduledNodes } from "../admin-backend/node/node.queries.js"; import { depKeysOfNodeRelationCache, makeCacheDependencyKey } from "../common/text-cache.js"; import { createOrOpenMainConnection } from "../connector/db-init/db-init.js"; import { loadRoutingCluster, loadSiteAndHomeNodeId, } from "../connector/db-init/load-routing-cluster.queries.js"; import { initializeSiteStructure } from "../connector/db-init/populate-routing-documents.js"; import { initializeMediaStorage } from "../connector/media-storage.js"; import { loadSiteSchema } from "../connector/site-schema/site-schema-factory.js"; import { readThemeConfFile } from "../connector/theme-conf/theme-conf-reader.js"; import { appConf, appVersion, cmsVersion, getCommonSchemaLib, platformLogger } from "../context.js"; import { toLiquidFilterHandler } from "../liquidjs-tools/liquidjs-filter.js"; import { extractRenderingContext } from "../liquidjs-tools/liquidjs-utils.js"; import { siteMaintenanceTask } from "../maintenance/maintenance-task.js"; import { createBackendPluginService, createPluginRenderingService, executeHook, } from "../plugin-services/make-backend-plugin-service.js"; import { createAssignLiquidTag, createInjectHtmlLiquidTag, } from "../rendered-site/helpers/liquid-tag-helpers.js"; import { createCorePlugin } from "./core-plugin/create-core-plugin.js"; import { initializeLiquidEngine } from "./liquid-init.js"; import { loadSiteAccess } from "./site-values-for-site-context.js"; export const alreadyLoaded = new WeakSet(); export async function loadSiteContext(regSite) { const { siteName, siteDir, dataDir, cacheDir, backupDir, fqdn } = regSite; const themeDir = join(siteDir, "theme"); const logger = platformLogger.createChildLog({ fqdn }); await ensureDirectory(dataDir); await ensureDirectory(cacheDir); await ensureDirectory(backupDir); const loaded = await loadSiteSchema({ logger, regSite, commonSchemaLib: getCommonSchemaLib(), pluginStaticConfigurations: appConf.plugins ?? {}, trusted: regSite.trusted, }); if (!loaded.upToDate) { return await loadMigrationSiteContext({ regSite, logger, themeDir, dataDir, cacheDir, backupDir, siteSchemaFormatVersion: loaded.siteSchemaFormatVersion, oldSchema: loaded.oldSchema, }); } const { siteSchema, plugins: initPlugins, siteConfiguration } = loaded; const mediaStorage = await initializeMediaStorage({ logger, dataDir }, { canCreate: !!appConf.generateMissingDatabases }); const { cn, logNextQuery, isNewDatabase } = await createOrOpenMainConnection(dataDir, { canCreate: !!appConf.generateMissingDatabases, logger, }); if (isNewDatabase) { await initializeSiteStructure(cn, siteSchema, logger, fqdn); } const { siteNodeId, homeNodeId } = await loadSiteAndHomeNodeId(cn); const homeRoutingCluster = await loadRoutingCluster({ cn, siteSchema }, { kind: "home", nodeId: homeNodeId, typeName: siteSchema.nodeTypes.home.typeName, siteNodeId, }); const access = await loadSiteAccess({ cn, siteSchema, siteNodeId }); const { imageCache, textCache, isClearedTextCache } = await createCaches({ cacheDir, logger, regSite, appConf, mediaStorage, siteSchema, }); if (isClearedTextCache) { await setInitialScheduledInvalidations({ cn, siteSchema, textCache }); } const liquidEngine = new Liquid({ root: [ join(themeDir, "templates"), ...initPlugins.flatMap((p) => p.initData.liquidRootDirectories), ], extname: ".liquid", jsTruthy: true, strictFilters: true, outputEscape: "escape", }); const themeTranslator = await loadSimpleTranslatorFromDirectory({ l10nDir: join(themeDir, "locales"), languages: siteSchema.languages, logger, }); const themeConf = await readThemeConfFile(themeDir); const plugins = initPlugins.map(toSitePlugin); const siteContext = { status: "ready", regSite, fqdn: regSite.fqdn, siteName, version: regSite.version, siteDir, dataDir, cacheDir, backupDir, siteUrl: regSite.siteUrl, siteSchema, themeDir, cn, siteConfiguration, siteNodeId, homeRoutingCluster, liquidEngine, mediaStorage, imageCache, textCache, themeTranslator, themeConf, plugins: new Map(plugins.map((plugin) => [plugin.pluginName, plugin])), pluginsBySlug: new Map(plugins.map((plugin) => [plugin.slug, plugin])), headTagsHandlers: [], logNextQuery, logger, renderingHooks: new Map(), hooks: new Map(), access, requestTracker: { pendingRequests: new Set(), requestQueue: [], state: "normal", }, }; initializeLiquidEngine(liquidEngine); const corePlugin = createCorePlugin(); for (const initPlugin of [corePlugin, ...initPlugins]) { postInitPlugin(siteContext, initPlugin); } await executeHook(siteContext, "initialized"); return siteContext; } async function loadMigrationSiteContext({ regSite, logger, themeDir, dataDir, cacheDir, backupDir, siteSchemaFormatVersion, oldSchema, }) { const languages = (isObj(oldSchema) && Array.isArray(oldSchema.languages) ? oldSchema.languages : undefined) ?? ["en"]; const { cn } = await createOrOpenMainConnection(dataDir, { canCreate: !!appConf.generateMissingDatabases, logger, }); const themeConf = await readThemeConfFile(themeDir); const migrationContext = { status: "migration", fqdn: regSite.fqdn, siteName: regSite.siteName, version: regSite.version, siteDir: regSite.siteDir, themeDir, dataDir, cacheDir, backupDir, siteUrl: regSite.siteUrl, themeConf, regSite, logger, cn, deprecatedSiteSchema: { ParoiCMSSiteSchemaFormatVersion: siteSchemaFormatVersion, languages, oldSchema, }, }; return migrationContext; } function toSitePlugin(plugin) { const { initData, ...rest } = plugin; return rest; } function postInitPlugin(siteContext, plugin) { const { initData } = plugin; for (const { filterName, handler, options } of initData.liquidFilters) { siteContext.liquidEngine.registerFilter(filterName, { handler: toLiquidFilterHandler(siteContext, async (value, { ctx, args }) => { const renderingContext = extractRenderingContext(ctx); const service = createPluginRenderingService(renderingContext, createBackendPluginService(siteContext, plugin)); return await handler(service, value, { ctx, args }); }), raw: options?.raw ?? false, }); } for (const registered of initData.liquidTags) { if (registered.tagKind === "injectHtml") { siteContext.liquidEngine.registerTag(registered.tagName, createInjectHtmlLiquidTag((renderingContext, parameters) => { const service = createPluginRenderingService(renderingContext, createBackendPluginService(siteContext, plugin)); return registered.handler(service, parameters); })); } else if (registered.tagKind === "assignToVar") { siteContext.liquidEngine.registerTag(registered.tagName, createAssignLiquidTag(function* (renderingContext, options) { const service = createPluginRenderingService(renderingContext, createBackendPluginService(siteContext, plugin)); return yield registered.handler(service, options); })); } } const uniqueHookNames = new Set(); for (const { hookName, handler, options } of initData.hooks) { if (options?.replace) { uniqueHookNames.add(hookName); } let handlers = siteContext.hooks.get(hookName); if (!handlers) { handlers = [{ handler, plugin }]; siteContext.hooks.set(hookName, handlers); } else { if (options?.replace) { siteContext.hooks.set(hookName, [{ handler, plugin }]); } else if (!uniqueHookNames.has(hookName)) { handlers.push({ handler, plugin }); } } } for (const { hookName, handler } of initData.renderingHooks) { let handlers = siteContext.renderingHooks.get(hookName); if (!handlers) { handlers = []; siteContext.renderingHooks.set(hookName, handlers); } handlers.push({ handler, plugin }); } siteContext.headTagsHandlers.push(...initData.headTagsHandlers); } async function createCaches(options) { const { cacheDir, logger, regSite, appConf, mediaStorage, siteSchema } = options; const { imageCache, isCleared: isClearedImageCache } = await createImageCacheEngine({ storage: { type: "sqlite", file: join(cacheDir, "image-cache.sqlite"), }, logger, getOriginalImage: async (mediaId) => { const image = await mediaStorage.getMediaWithBinary(mediaId); if (!image) throw new ApiError(`Cannot find image '${mediaId}'`, 404); return image.binaryFile; }, qualityPolicy: siteSchema.imageQualityPolicy, timeToIdle: appConf.cacheTimeToIdle === "infinite" ? undefined : appConf.cacheTimeToIdle === "disabled" ? "0d" : appConf.cacheTimeToIdle, debugMode: appConf.logLevel === "debug", }); let { textCache, isCleared: isClearedTextCache } = appConf.cacheTimeToIdle === "disabled" ? createNoTextCacheSystem() : await createTextCacheSystem({ appVersion: `paroicms:${cmsVersion};app:${appVersion ?? "*"}`, storage: { type: "sqlite", file: join(cacheDir, "text-cache.sqlite"), }, logger, timeToIdle: appConf.cacheTimeToIdle === "infinite" ? undefined : appConf.cacheTimeToIdle, }); if (isClearedImageCache && !isClearedTextCache) { await textCache.clearCache(); isClearedTextCache = true; } if (!alreadyLoaded.has(regSite)) { if (appConf.clearCacheAfterStart) { if (appConf.clearImageCacheAfterStart) { await imageCache.clearCache(); } if (!isClearedTextCache) { await textCache.clearCache(); isClearedTextCache = true; } } else { await siteMaintenanceTask({ imageCache, textCache }); } alreadyLoaded.add(regSite); } return { imageCache, textCache, isClearedTextCache }; } async function setInitialScheduledInvalidations(ctx) { const nodes = await getScheduledNodes(ctx); for (const { nodeId, publishDate } of nodes) { const newDocDepKeys = await depKeysOfNodeRelationCache(ctx, { parentOfNodeId: nodeId, relation: "children", }); const eventKey = makeCacheDependencyKey({ nodeId }); await ctx.textCache.scheduleInvalidation(eventKey, newDocDepKeys, new Date(publishDate)); } } //# sourceMappingURL=load-site-context.js.map