Files
tibi-svelte-starter/api/hooks/lib/ssr-server.js
T

147 lines
5.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const { apiSsrBaseURL, ssrPublishCheckCollections } = require("../config")
/**
* api request via server, cache result in context.ssrCache
* should be elimated in client code via tree shaking
*
* @param {string} cacheKey
* @param {string} endpoint
* @param {string} query
* @param {ApiOptions} options
* @returns {ApiResult<any>}
*/
function ssrRequest(cacheKey, endpoint, query, options) {
let url = endpoint + (query ? "?" + query : "")
// track which collections/entries contribute to this SSR render
// endpoint may contain path segments (e.g. "content/abc123") or query strings
const collectionName = endpoint.split("?")[0].split("/")[0]
if (ssrPublishCheckCollections?.includes(endpoint)) {
// @ts-ignore
let validUntil = context.ssrCacheValidUntil
// check in db for publish date to invalidate cache
const _optionsPublishSearch = Object.assign(
{},
{ filter: options?.filter },
{
selector: { publication: 1 },
projection: null,
}
)
const publishSearch = context.db.find(endpoint, _optionsPublishSearch)
publishSearch?.forEach((item) => {
const publicationFrom = item.publication?.from ? new Date(item.publication.from.unixMilli()) : null
const publicationTo = item.publication?.to ? new Date(item.publication.to.unixMilli()) : null
if (publicationFrom && publicationFrom > new Date()) {
// entry has a publish date that is further in in the future than current, set global validUntil
if (validUntil == null || validUntil > publicationFrom) {
validUntil = publicationFrom
}
}
if (publicationTo && publicationTo > new Date()) {
// entry has a unpublish date that is further in in the future than current, set global validUntil
if (validUntil == null || validUntil > publicationTo) {
validUntil = publicationTo
}
}
})
// @ts-ignore
context.ssrCacheValidUntil = validUntil
}
// console.log("############ FETCHING ", apiSsrBaseURL + url)
const response = context.http.fetch(apiSsrBaseURL + url, {
method: options.method,
headers: options.headers,
})
// console.log(JSON.stringify(response.headers, null, 2))
const json = response.body.json()
const count = parseInt(response.headers["X-Results-Count"] || "0")
// json is go data structure and incompatible with js, so we need to convert it
const r = { data: JSON.parse(JSON.stringify(json)), count: count }
// track dependencies: "col:id" for single-entry, "col:*" for list queries
// @ts-ignore dynamic property set by get_read.js
if (context.ssrDeps) {
let entryId = null
if (!Array.isArray(r.data) && r.data && r.data.id) {
// direct ID lookup (COLLECTION/ID) API returned single object
entryId = r.data.id
} else if (options?.limit === 1 && Array.isArray(r.data) && r.data.length === 1 && r.data[0] && r.data[0].id) {
// filter-based detail query with limit:1
entryId = r.data[0].id
}
if (entryId) {
// @ts-ignore
context.ssrDeps[collectionName + ":" + entryId] = true
} else {
// list query any change to this collection affects this page
// @ts-ignore
context.ssrDeps[collectionName + ":*"] = true
}
// --- EXTENSION: Track dependencies from lookup and aggregate ---
// Both `lookup` and `aggregate` parameters can inject data from other collections.
// We must invalidate the SSR cache if any of those referenced collections change.
if (options && options.lookup) {
const lookups = typeof options.lookup === "string" ? options.lookup.split(",") : [];
for (const l of lookups) {
// format: "fieldPath:collectionName"
const parts = l.split(":");
if (parts.length > 1) {
const targetCollection = parts[parts.length - 1];
// @ts-ignore
context.ssrDeps[targetCollection + ":*"] = true;
}
}
}
const rawAggregate = (options && options.aggregate) || (options && options.params && options.params.aggregate);
if (rawAggregate) {
const aggregates = typeof rawAggregate === "string"
? rawAggregate.split(",")
: [];
for (const a of aggregates) {
// simple format: "collectionName:foreignField:..."
// json format: '{"collection":"comments",...}'
try {
if (a.startsWith("{")) {
const parsed = JSON.parse(a);
if (parsed && parsed.collection) {
// @ts-ignore
context.ssrDeps[parsed.collection + ":*"] = true;
}
} else {
const parts = a.split(":");
if (parts.length > 0) {
const targetCollection = parts[0];
// @ts-ignore
context.ssrDeps[targetCollection + ":*"] = true;
}
}
} catch (e) {
// silently ignore parse errors here
}
}
}
// --- END EXTENSION ---
}
// @ts-ignore
context.ssrCache[cacheKey] = r
return r
}
module.exports = {
ssrRequest,
}