studiocms
Version:
Astro Native CMS for AstroDB. Built from the ground up by the Astro community.
416 lines (380 loc) • 13.1 kB
text/typescript
import { eq } from 'astro:db';
import { Effect, genLogger } from '../../../effect.js';
import {
FolderListMapID,
FolderTreeMapID,
SiteConfigMapID,
StudioCMSPkgId,
VersionMapID,
} from '../consts.js';
import { AstroDB, GetVersionFromNPM, SDKCore_FolderTree } from '../effect/index.js';
import {
tsPageContent,
tsPageData,
tsPageDataCategories,
tsPageDataTags,
tsPageFolderStructure,
tsPermissions,
} from '../tables.js';
import type {
tsPageContentSelect,
tsPageDataCategoriesSelect,
tsPageDataSelect,
tsPageDataTagsSelect,
tsPageFolderSelect,
tsPermissionsSelect,
} from '../types/index.js';
import {
_ClearUnknownError,
_clearLibSQLError,
CacheContext,
folderListReturn,
folderTreeReturn,
isCacheEnabled,
pageDataReturn,
siteConfigReturn,
versionReturn,
} from '../utils.js';
import { SDKCore_CLEAR } from './clear.js';
import { SDKCore_CONFIG, type StudioCMSSiteConfig } from './config.js';
import { SDKCore_GET } from './get.js';
/**
* Provides update operations for StudioCMS entities such as pages, tags, categories, permissions, folders, versions, and site configuration.
*
* @remarks
* This service is part of the StudioCMS SDK core and is responsible for updating various resources in the database and cache.
* It handles error catching for database operations and ensures cache consistency after updates.
*
* @example
* ```typescript
* const updateService = yield* SDKCore_UPDATE;
* yield* updateService.pageContent({ id: '...', ... });
* ```
*
* @class SDKCore_UPDATE
* @extends Effect.Service
*
* @property pageContent - Updates a page's content in the database.
* @property tags - Updates a tag in the database.
* @property categories - Updates a category in the database.
* @property permissions - Updates a permission in the database.
* @property folderTree - Updates the folder tree structure and cache.
* @property folderList - Updates the folder list and cache.
* @property folder - Updates a folder and refreshes related caches.
* @property latestVersion - Updates and caches the latest StudioCMS version.
* @property siteConfig - Updates the site configuration and cache.
* @property page.byId - Updates a page by its ID, including content and data, and refreshes caches.
* @property page.bySlug - Updates a page by its slug, including content and data, and refreshes caches.
*
* @throws StudioCMS_SDK_Error - If an error occurs during any update operation.
*/
export class SDKCore_UPDATE extends Effect.Service<SDKCore_UPDATE>()(
'studiocms/sdk/SDKCore/modules/update',
{
dependencies: [
AstroDB.Default,
SDKCore_CLEAR.Default,
SDKCore_FolderTree.Default,
SDKCore_GET.Default,
GetVersionFromNPM.Default,
SDKCore_CONFIG.Default,
],
effect: genLogger('studiocms/sdk/SDKCore/modules/update/effect')(function* () {
const [
dbService,
CLEAR,
GET,
{ siteConfig: sdkSiteConfig },
{ buildFolderTree, getAvailableFolders },
{ pages, FolderList, folderTree, version, siteConfig },
getVersionFromNPM,
] = yield* Effect.all([
AstroDB,
SDKCore_CLEAR,
SDKCore_GET,
SDKCore_CONFIG,
SDKCore_FolderTree,
CacheContext,
GetVersionFromNPM,
]);
const UPDATE = {
/**
* Updates a page content in the database.
*
* @param data - The data to update in the page content table.
* @returns A promise that resolves to the updated page content.
* @throws {StudioCMS_SDK_Error} If an error occurs while updating the page content.
*/
pageContent: dbService.makeQuery((ex, data: tsPageContentSelect) =>
ex((db) =>
db
.update(tsPageContent)
.set(data)
.where(eq(tsPageContent.id, data.id))
.returning()
.get()
).pipe(
Effect.catchTags({
LibSQLClientError: (cause) => _clearLibSQLError('UPDATE.pageContent', cause),
})
)
),
/**
* Updates a tag in the database.
*
* @param data - The data to update in the page data tags table.
* @returns A promise that resolves to the updated tag.
* @throws {StudioCMS_SDK_Error} If an error occurs while updating the tag.
*/
tags: dbService.makeQuery((ex, data: tsPageDataTagsSelect) =>
ex((db) =>
db
.update(tsPageDataTags)
.set(data)
.where(eq(tsPageDataTags.id, data.id))
.returning()
.get()
).pipe(
Effect.catchTags({
LibSQLClientError: (cause) => _clearLibSQLError('UPDATE.tags', cause),
})
)
),
/**
* Updates a category in the database.
*
* @param data - The data to update in the page data categories table.
* @returns A promise that resolves to the updated category.
* @throws {StudioCMS_SDK_Error} If an error occurs while updating the category.
*/
categories: dbService.makeQuery((ex, data: tsPageDataCategoriesSelect) =>
ex((db) =>
db
.update(tsPageDataCategories)
.set(data)
.where(eq(tsPageDataCategories.id, data.id))
.returning()
.get()
).pipe(
Effect.catchTags({
LibSQLClientError: (cause) => _clearLibSQLError('UPDATE.categories', cause),
})
)
),
/**
* Updates a permission in the database.
*
* @param data - The data to update in the permissions table.
* @returns A promise that resolves to the updated permission.
* @throws {StudioCMS_SDK_Error} If an error occurs while updating the permission.
*/
permissions: dbService.makeQuery((ex, data: tsPermissionsSelect) =>
ex((db) =>
db
.update(tsPermissions)
.set(data)
.where(eq(tsPermissions.user, data.user))
.returning()
.get()
).pipe(
Effect.catchTags({
LibSQLClientError: (cause) => _clearLibSQLError('UPDATE.permissions', cause),
})
)
),
/**
* Updates the folder tree structure and cache.
* @returns An Effect that resolves when the folder tree is updated.
* @throws {LibSQLDatabaseError} If a database error occurs during the update.
*/
folderTree: Effect.gen(function* () {
const status = yield* isCacheEnabled;
yield* CLEAR.folderTree();
const newFolderTree = yield* buildFolderTree;
if (status) {
folderTree.set(FolderTreeMapID, folderTreeReturn(newFolderTree));
}
}).pipe(
Effect.catchTags({
LibSQLClientError: (cause) => _clearLibSQLError('UPDATE.folderTree', cause),
})
),
/**
* Updates the folder list and cache.
* @returns An Effect that resolves when the folder list is updated.
* @throws {LibSQLDatabaseError} If a database error occurs during the update.
*/
folderList: Effect.gen(function* () {
const status = yield* isCacheEnabled;
yield* CLEAR.folderList();
const folderList = yield* getAvailableFolders;
if (status) {
FolderList.set(FolderListMapID, folderListReturn(folderList));
}
}).pipe(
Effect.catchTags({
LibSQLClientError: (cause) => _clearLibSQLError('UPDATE.folderList', cause),
})
),
/**
* Updates a folder in the database and refreshes related caches.
* @param data - The folder data to update.
* @returns An Effect that resolves to the updated folder data.
* @throws {LibSQLDatabaseError} If a database error occurs during the update.
*/
folder: (data: tsPageFolderSelect) =>
Effect.gen(function* () {
const updated = yield* dbService.execute((db) =>
db
.update(tsPageFolderStructure)
.set(data)
.where(eq(tsPageFolderStructure.id, data.id))
.returning()
.get()
);
yield* UPDATE.folderList;
yield* UPDATE.folderTree;
return updated;
}).pipe(
Effect.catchTags({
LibSQLClientError: (cause) => _clearLibSQLError('UPDATE.folder', cause),
})
),
/**
* Updates the latest StudioCMS version in the cache.
* @returns An Effect that resolves to the latest version.
* @throws {UnknownException} If an error occurs while fetching the latest version.
*/
latestVersion: () =>
Effect.gen(function* () {
const status = yield* isCacheEnabled;
const latestVersion = yield* getVersionFromNPM.get(StudioCMSPkgId);
const newVersion = versionReturn(latestVersion);
if (!status) return newVersion;
version.set(VersionMapID, newVersion);
return newVersion;
}).pipe(
Effect.catchTags({
UnknownException: (cause) => _ClearUnknownError('UPDATE.latestVersion', cause),
})
),
/**
* Updates the site configuration in the database and cache.
* @param data - The new site configuration data.
* @returns An Effect that resolves to the updated site configuration.
* @throws {LibSQLDatabaseError} If a database error occurs during the update.
* @throws {UnknownException} If an unknown error occurs during the update.
*/
siteConfig: (data: Omit<StudioCMSSiteConfig, '_config_version'>) =>
Effect.gen(function* () {
const status = yield* isCacheEnabled;
const newSiteConfig = yield* sdkSiteConfig.update(data);
if (!newSiteConfig) return;
const returnConfig = siteConfigReturn(newSiteConfig.data);
if (!status) return returnConfig;
siteConfig.set(SiteConfigMapID, returnConfig);
return returnConfig;
}),
page: {
/**
* Updates a page by its ID, including content and data, and refreshes caches.
* @param id - The ID of the page to update.
* @param data - The new page data and content to update.
* @returns An Effect that resolves to the updated page data, or undefined if not found.
* @throws {LibSQLDatabaseError} If a database error occurs during the update.
* @throws {UnknownException} If an unknown error occurs during the update.
*/
byId: (
id: string,
data: {
pageData: tsPageDataSelect;
pageContent: tsPageContentSelect;
}
) => {
const updatePage = dbService.makeQuery((ex, data: tsPageDataSelect) =>
ex((db) =>
db.update(tsPageData).set(data).where(eq(tsPageData.id, data.id)).returning().get()
)
);
return Effect.gen(function* () {
const status = yield* isCacheEnabled;
yield* updatePage(data.pageData);
yield* UPDATE.pageContent(data.pageContent);
const rawUpdated = yield* GET.page.byId(id);
if (!rawUpdated) {
return undefined;
}
const returnData = pageDataReturn(rawUpdated.data);
if (!status) {
return returnData;
}
yield* CLEAR.folderList();
yield* CLEAR.folderTree();
yield* CLEAR.pages();
return returnData;
}).pipe(
Effect.catchTags({
LibSQLClientError: (cause) => _clearLibSQLError('UPDATE.page.byId', cause),
UnknownException: (cause) => _ClearUnknownError('UPDATE.page.byId', cause),
})
);
},
/**
* Updates a page by its slug, including content and data, and refreshes caches.
* @param slug - The slug of the page to update.
* @param data - The new page data and content to update.
* @returns An Effect that resolves to the updated page data, or undefined if not found.
* @throws {LibSQLDatabaseError} If a database error occurs during the update.
* @throws {UnknownException} If an unknown error occurs during the update.
*/
bySlug: (
slug: string,
data: {
pageData: tsPageDataSelect;
pageContent: tsPageContentSelect;
}
) => {
const updatePage = dbService.makeQuery((ex, data: tsPageDataSelect) =>
ex((db) =>
db.update(tsPageData).set(data).where(eq(tsPageData.id, data.id)).returning().get()
)
);
return Effect.gen(function* () {
const status = yield* isCacheEnabled;
if (!status) {
yield* updatePage(data.pageData);
yield* UPDATE.pageContent(data.pageContent);
const rawUpdated = yield* GET.page.byId(data.pageData.id);
if (!rawUpdated) {
return undefined;
}
return pageDataReturn(rawUpdated.data);
}
const cachedPage = Array.from(pages.values()).find((page) => page.data.slug === slug);
if (!cachedPage) {
return undefined;
}
yield* updatePage(data.pageData);
yield* UPDATE.pageContent(data.pageContent);
const rawUpdated = yield* GET.page.byId(data.pageData.id);
if (!rawUpdated) {
return undefined;
}
const returnData = pageDataReturn(rawUpdated.data);
yield* CLEAR.folderList();
yield* CLEAR.folderTree();
yield* CLEAR.pages();
return returnData;
}).pipe(
Effect.catchTags({
LibSQLClientError: (cause) => _clearLibSQLError('UPDATE.page.byId', cause),
UnknownException: (cause) => _ClearUnknownError('UPDATE.page.byId', cause),
})
);
},
},
};
return UPDATE;
}),
}
) {}