@paroicms/server
Version:
The ParoiCMS server
312 lines • 12.9 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 { loadSiteSchema } from "../connector/site-schema/site-schema-factory.js";
import { readThemeConfFile } from "../connector/theme-conf/theme-conf-reader.js";
import { appConf, appPackage, cmsVersion, getCommonSchemaLib, getPreviewSecret, 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 { createCorePlugin } from "./core-plugin/create-core-plugin.js";
import { initializeLiquidEngine } from "./liquid-init.js";
import { initializeMediaStorage, makeMediaStorageDependsOn } from "./main-medias-databases.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 { cn, logNextQuery, isNewDatabase, databaseId: mainDatabaseId, } = await createOrOpenMainConnection(dataDir, {
canCreate: !!appConf.generateMissingDatabases,
logger,
}, { fqdn });
const mediaStorage = await initializeMediaStorage({ logger, dataDir }, {
canCreate: !!appConf.generateMissingDatabases,
dependsOn: makeMediaStorageDependsOn({ mainDatabaseId }),
});
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, isFreshCache } = await createCaches({
cacheDir,
logger,
regSite,
appConf,
siteSchema,
mainDatabaseId,
appPackage,
getMediaStorage: () => siteContext.mediaStorage,
mediaStorageDatabaseId: mediaStorage.databaseId,
});
await siteMaintenanceTask({ imageCache, textCache, cn, logger });
if (isFreshCache) {
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 { setLiquidTag, outLiquidTag } = initializeLiquidEngine(liquidEngine);
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,
previewSecret: getPreviewSecret(),
liquidEngine,
setLiquidTag,
outLiquidTag,
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",
},
};
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,
}, { fqdn: regSite.fqdn });
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.setLiquidTagHandlers) {
siteContext.setLiquidTag.registerHandler(registered.fnName, function* (renderingContext, options) {
const service = createPluginRenderingService(renderingContext, createBackendPluginService(siteContext, plugin));
return yield registered.handler(service, {
...options,
document: renderingContext.mainDocument,
});
});
}
for (const registered of initData.outLiquidTagHandlers) {
siteContext.outLiquidTag.registerHandler(registered.fnName, (renderingContext, { positionedParameters, namedParameters }) => {
const service = createPluginRenderingService(renderingContext, createBackendPluginService(siteContext, plugin));
return registered.handler(service, {
positionedParameters,
namedParameters,
document: renderingContext.mainDocument,
});
});
}
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, siteSchema, mainDatabaseId, appPackage, getMediaStorage, mediaStorageDatabaseId, } = options;
const imageCacheDependsOn = `db:medias:${mediaStorageDatabaseId}`;
const { imageCache, databaseId: imageCacheDatabaseId } = await createImageCacheEngine({
storage: {
type: "sqlite",
file: join(cacheDir, "image-cache.sqlite"),
},
logger,
dependsOn: imageCacheDependsOn,
getOriginalImage: async (mediaId) => {
const image = await getMediaStorage().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",
});
const appName = appPackage?.name ?? "unamed";
const appVersionSegment = appPackage?.version ?? "*";
const textCacheDependsOn = `db:main:${mainDatabaseId};db:image-cache:${imageCacheDatabaseId};pkg:@paroicms/server:${cmsVersion};app:${appName}:${appVersionSegment}`;
const { textCache, isFresh: isFreshTextCache } = appConf.cacheTimeToIdle === "disabled"
? createNoTextCacheSystem()
: await createTextCacheSystem({
dependsOn: textCacheDependsOn,
storage: {
type: "sqlite",
file: join(cacheDir, "text-cache.sqlite"),
},
logger,
timeToIdle: appConf.cacheTimeToIdle === "infinite" ? undefined : appConf.cacheTimeToIdle,
});
let isFreshCache = isFreshTextCache;
if (!alreadyLoaded.has(regSite)) {
if (appConf.clearCacheAfterStart) {
if (appConf.clearImageCacheAfterStart) {
await imageCache.clearCache();
}
if (!isFreshCache) {
await textCache.clearCache();
await imageCache.clearCache();
isFreshCache = true;
}
}
alreadyLoaded.add(regSite);
}
return { imageCache, textCache, isFreshCache };
}
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