@paroicms/server
Version:
The ParoiCMS server
298 lines • 12.2 kB
JavaScript
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