diff --git a/package.json b/package.json index 3b66ba6..4c9bbe5 100644 --- a/package.json +++ b/package.json @@ -20,8 +20,8 @@ }, "prettier": "@harperdb/code-guidelines/prettier", "scripts": { - "dev": "harperdb dev .", - "start": "harperdb run .", + "dev": "harper dev .", + "start": "harper run .", "lint": "eslint . --config eslint.config.mjs", "test": "npx vitest run --config vitest.config.js" }, diff --git a/resources/BulkUpload.js b/resources/BulkUpload.js index 56d5256..4ed9ea2 100644 --- a/resources/BulkUpload.js +++ b/resources/BulkUpload.js @@ -55,10 +55,13 @@ export default class BulkUpload extends Resource { * { urlList: Array, refreshInterval: number } * { sitemap: string, isIndex: boolean, pageRefreshInterval: number, sitemapRefreshInterval: number } * - * @param {object} data - Bulk upload request payload. + * @param {object} target - Request target. + * @param {Promise} data - Bulk upload request payload (must be awaited). + * @param {object} context - Request context. * @returns {Promise} - Status code and message. */ - async post(data) { + static async post(target, data, context) { + data = await data; if (!data) { return { status: 400, @@ -68,13 +71,13 @@ export default class BulkUpload extends Resource { } // Add single URL to job queue - if (data.url) return await this.handleSingleUrl(data); + if (data.url) return await BulkUpload.handleSingleUrl(data); // Add list of URLs to job queue - if (data.urlList) return await this.handleUrlList(data); + if (data.urlList) return await BulkUpload.handleUrlList(data); // Parse sitemap and add URLs to job queue - if (data.sitemap) return await this.handleSitemap(data); + if (data.sitemap) return await BulkUpload.handleSitemap(data); return { status: 400, @@ -87,7 +90,7 @@ export default class BulkUpload extends Resource { * Handle PATCH requests. * @returns {object} - 405 error for PATCH method */ - async patch() { + static async patch(target, data, context) { return { status: 405, headers: { 'Content-Type': 'application/json' }, @@ -101,7 +104,7 @@ export default class BulkUpload extends Resource { /** Handle PUT requests. * @returns {object} - 405 error for PUT method */ - async put() { + static async put(target, data, context) { return { status: 405, headers: { 'Content-Type': 'application/json' }, @@ -115,7 +118,7 @@ export default class BulkUpload extends Resource { /** Handle DELETE requests. * @returns {object} - 405 error for DELETE method */ - async delete() { + static async delete(target, context) { return { status: 405, headers: { 'Content-Type': 'application/json' }, @@ -129,7 +132,7 @@ export default class BulkUpload extends Resource { /** Handle GET requests. * @returns {object} - 405 error for GET method */ - async get() { + static async get(target, context) { return { status: 405, headers: { 'Content-Type': 'application/json' }, @@ -147,7 +150,7 @@ export default class BulkUpload extends Resource { * @param {number} data.refreshInterval - Refresh interval in milliseconds. * @returns {Promise} - Status code and message. */ - async handleSingleUrl({ url, refreshInterval }) { + static async handleSingleUrl({ url, refreshInterval }) { try { if (!url || typeof url !== 'string') { return { @@ -181,7 +184,7 @@ export default class BulkUpload extends Resource { * @returns {Promise} - Status code and message. * @throws {object} - If the entire batch fails, throws an object with the URLs and error message. */ - async handleUrlList({ urlList, refreshInterval }) { + static async handleUrlList({ urlList, refreshInterval }) { try { if (!urlList || !Array.isArray(urlList) || urlList.length === 0) { return { @@ -237,7 +240,7 @@ export default class BulkUpload extends Resource { * @param {number} data.sitemapRefreshInterval - Sitemap refresh interval in milliseconds. * @returns {Promise} - Status code and message. */ - async handleSitemap({ sitemap, isIndex, pageRefreshInterval, sitemapRefreshInterval }) { + static async handleSitemap({ sitemap, isIndex, pageRefreshInterval, sitemapRefreshInterval }) { try { if (!sitemap || typeof sitemap !== 'string') { return { diff --git a/resources/CacheMetrics.js b/resources/CacheMetrics.js index d1e81ac..5d8983f 100644 --- a/resources/CacheMetrics.js +++ b/resources/CacheMetrics.js @@ -39,9 +39,11 @@ const { hdb_analytics } = databases.system; export default class CacheMetrics extends Resource { /** * Retrieves page content cache metrics from the last 60 seconds. + * @param {object} target - Request target. + * @param {object} context - Request context. * @returns {Promise>} - An array of metric objects matching the query. */ - async get() { + static async get(target, context) { logger.info('Retrieving page content cache metrics from the last 60 seconds'); // Compute rolling time window: [now - 60s, now] diff --git a/resources/PageContent.js b/resources/PageContent.js index 49d9663..2195d39 100644 --- a/resources/PageContent.js +++ b/resources/PageContent.js @@ -35,7 +35,7 @@ * - All write methods (POST, PUT, PATCH, DELETE) are intentionally disabled to preserve read-only semantics. */ -import { server } from 'harperdb'; +import { server } from 'harper'; import Job from './JobQueue.js'; import { allowedReadRoles, allowStaleContent } from '../utils/constants.js'; import { parseQuery } from '../utils/parse.js'; @@ -56,6 +56,70 @@ let usageCache = new Map(); * - Normalizes headers, injects error handling, and generates error pages on failures. * - Responds with 405 "Method Not Allowed" for unsupported HTTP methods. */ +/** + * Merge and normalize headers from page + context. + * Applies overrides for content errors and caching rules. + * + * @param {object} pageHeaders - Headers returned by source/page. + * @param {Headers} responseHeaders - Existing response headers. + * @param {boolean} contentError - Whether content retrieval failed. + * @param {number} contentLength - Final length of the content. + * @returns {Headers} - Normalized headers object. + */ +function getHeaders(pageHeaders, responseHeaders, contentError, contentLength) { + const pgHeaders = JSON.parse(pageHeaders || {}); + const headers = responseHeaders || new Headers(); + for (const [key, value] of Object.entries(pgHeaders)) { + switch (key) { + case 'server-timing': { + headers.append(key, value); + break; + } + + case 'content-type': { + headers.set(key, contentError ? 'text/markdown; charset=utf-8' : value); + break; + } + + case 'content-length': { + headers.set(key, contentLength); + break; + } + + default: { + headers.set(key, value); + break; + } + } + } + + if (contentError) { + headers.set('retry-after', 300); // 5 minute retry for errors + headers.delete('cache-control'); + headers.delete('content-encoding'); // Blob error page is not gzipped to save time + } + + return headers; +} + +/** + * Returns 405 Method Not Allowed response + * for unsupported HTTP methods (POST, PUT, PATCH, DELETE) + * with a markdown message indicating the allowed method (GET) + * @returns {object} - object with status, headers, and markdown content + */ +function methodNotAllowed() { + const content = '# Method Not Allowed \n\nPlease use GET to retrieve the page content.'; + const contentType = 'text/markdown; charset=utf-8'; + const contentLength = Buffer.byteLength(content, 'utf8'); + + return { + status: 405, + data: { data: content, contentType }, + headers: new Headers({ 'content-type': contentType, 'content-length': contentLength }), + }; +} + export default class Content extends Resource { static directURLMapping = true; @@ -75,15 +139,15 @@ export default class Content extends Resource { * and scheduling a background refresh job. * - Merges response headers with overrides based on error conditions. * - * @param {object} query - Request query. + * @param {object} target - Request target (extends URLSearchParams). + * @param {object} context - Request context. * @returns {Promise} - Response object with status, headers, and content. */ - async get(query) { - const queryPath = this.getId(); - const context = this.getContext(); + static async get(target, context) { + const queryPath = target.id; // Parse the request into host, path, and query string - const urlCacheKey = parseQuery(queryPath, query, context); + const urlCacheKey = parseQuery(queryPath, target, context); logger.info(`Fetching content for: ${urlCacheKey}`); // Check cache first, update lastAccessed if more than 1 minute old @@ -134,7 +198,7 @@ export default class Content extends Resource { } const contentLength = contentError ? Buffer.byteLength(blob, 'utf8') : page.contentLength; - const respHeaders = this.getHeaders(page.headers, context.responseHeaders, contentError, contentLength); + const respHeaders = getHeaders(page.headers, context.responseHeaders, contentError, contentLength); return { headers: respHeaders, @@ -144,88 +208,24 @@ export default class Content extends Resource { }; } - /** - * Merge and normalize headers from page + context. - * Applies overrides for content errors and caching rules. - * - * @param {object} pageHeaders - Headers returned by source/page. - * @param {Headers} responseHeaders - Existing response headers. - * @param {boolean} contentError - Whether content retrieval failed. - * @param {number} contentLength - Final length of the content. - * @returns {Headers} - Normalized headers object. - */ - getHeaders(pageHeaders, responseHeaders, contentError, contentLength) { - const pgHeaders = JSON.parse(pageHeaders || {}); - const headers = responseHeaders || new Headers(); - for (const [key, value] of Object.entries(pgHeaders)) { - switch (key) { - case 'server-timing': { - headers.append(key, value); - break; - } - - case 'content-type': { - headers.set(key, contentError ? 'text/markdown; charset=utf-8' : value); - break; - } - - case 'content-length': { - headers.set(key, contentLength); - break; - } - - default: { - headers.set(key, value); - break; - } - } - } - - if (contentError) { - headers.set('retry-after', 300); // 5 minute retry for errors - headers.delete('cache-control'); - headers.delete('content-encoding'); // Blob error page is not gzipped to save time - } - - return headers; - } - - /** - * Returns 405 Method Not Allowed response - * for unsupported HTTP methods (POST, PUT, PATCH, DELETE) - * with a markdown message indicating the allowed method (GET) - * @returns {object} - object with status, headers, and markdown content - */ - methodNotAllowed() { - const content = '# Method Not Allowed \n\nPlease use GET to retrieve the page content.'; - const contentType = 'text/markdown; charset=utf-8'; - const contentLength = Buffer.byteLength(content, 'utf8'); - - return { - status: 405, - data: { data: content, contentType }, - headers: new Headers({ 'content-type': contentType, 'content-length': contentLength }), - }; - } - /** @returns {object} - 405 error for POST method */ - post() { - return this.methodNotAllowed(); + static post() { + return methodNotAllowed(); } /** @returns {object} - 405 error for PUT method */ - put() { - return this.methodNotAllowed(); + static put() { + return methodNotAllowed(); } /** @returns {object} - 405 error for PATCH method */ - patch() { - return this.methodNotAllowed(); + static patch() { + return methodNotAllowed(); } /** @returns {object} - 405 error for DELETE method */ - delete() { - return this.methodNotAllowed(); + static delete() { + return methodNotAllowed(); } } diff --git a/resources/PageFilter.js b/resources/PageFilter.js index 6b5e1b8..c9ac4a1 100644 --- a/resources/PageFilter.js +++ b/resources/PageFilter.js @@ -39,11 +39,12 @@ export default class Filter extends Resource { /** * Get filters for a given path. * - * @param {string} pathname - Path identifier for the page. + * @param {object} target - Request target (extends URLSearchParams); target.id is the path. + * @param {object} context - Request context. * @returns {Promise} Result of the get operation. */ - async get() { - const pathname = this.getId(); + static async get(target, context) { + const pathname = target.id; if (!pathname || typeof pathname !== 'string') { return { status: 400, @@ -68,13 +69,16 @@ export default class Filter extends Resource { /** * Create or update a filter record for a path. * - * @param {object} data - The filter data. + * @param {object} target - Request target. + * @param {Promise} data - The filter data (must be awaited). * @param {string} data.path - Path identifier for the page (gets normalized internally). * @param {string} data.filters - Comma-separated CSS selectors string. * Example: `"header, #id1, .class2"`. + * @param {object} context - Request context. * @returns {Promise} Result of the post operation, or an error response if validation fails. */ - async post(data) { + static async post(target, data, context) { + data = await data; if (!data.path || typeof data.path !== 'string') { return { status: 400, @@ -113,11 +117,12 @@ export default class Filter extends Resource { /** * Delete filters for a given path. * - * @param {string} pathname - Path identifier for the page. + * @param {object} target - Request target; target.id is the path identifier. + * @param {object} context - Request context. * @returns {Promise} Result of the delete operation. */ - async delete() { - const pathname = this.getId(); + static async delete(target, context) { + const pathname = target.id; if (!pathname || typeof pathname !== 'string') { return { status: 400, @@ -145,7 +150,7 @@ export default class Filter extends Resource { * Method not allowed, must use PUT to create/update filters. * @returns {object} - 405 error for PATCH method */ - async patch() { + static async patch(target, data, context) { return { status: 405, headers: { 'Content-Type': 'application/json' }, @@ -160,7 +165,7 @@ export default class Filter extends Resource { * Method not allowed, must use POST to create/update filters. * @returns {object} - 405 error for PUT method */ - async put() { + static async put(target, data, context) { return { status: 405, headers: { 'Content-Type': 'application/json' }, diff --git a/resources/Worker.js b/resources/Worker.js index eba9456..7b1c3dd 100644 --- a/resources/Worker.js +++ b/resources/Worker.js @@ -32,7 +32,7 @@ */ import { performance } from 'perf_hooks'; -import { server } from 'harperdb'; +import { server } from 'harper'; import Job from './JobQueue.js'; import { pageSource } from '../utils/pageSource.js'; diff --git a/resources/index.js b/resources/index.js index 74da41c..cc8fbc9 100644 --- a/resources/index.js +++ b/resources/index.js @@ -31,7 +31,7 @@ */ import '../utils/cachedDNS.js'; // Import ensures DNS cache and global agent are set up -import { server } from 'harperdb'; +import { server } from 'harper'; import { schedulerNode, nodeName, deliveryNodes, workerNodes } from '../utils/constants.js'; import Content from './PageContent.js'; import Filter from './PageFilter.js'; diff --git a/utils/converter.js b/utils/converter.js index 0b7f7da..8c9a4e9 100644 --- a/utils/converter.js +++ b/utils/converter.js @@ -25,7 +25,7 @@ import { performance } from 'node:perf_hooks'; import { NodeHtmlMarkdown } from 'node-html-markdown'; -import { server } from 'harperdb'; +import { server } from 'harper'; /** * Converts cleaned HTML content into markdown format using node-html-markdown diff --git a/utils/filter.js b/utils/filter.js index 37c09f0..b8f7882 100644 --- a/utils/filter.js +++ b/utils/filter.js @@ -33,7 +33,7 @@ import { performance } from 'node:perf_hooks'; import { parse } from 'node-html-parser'; -import { server } from 'harperdb'; +import { server } from 'harper'; import Filter from '../resources/PageFilter.js'; /** diff --git a/utils/handleOnDemand.js b/utils/handleOnDemand.js index 9ffc636..98bc54d 100644 --- a/utils/handleOnDemand.js +++ b/utils/handleOnDemand.js @@ -26,7 +26,7 @@ * - Ensures database consistency by persisting only successfully rendered content. */ -import { databases } from 'harperdb'; +import { databases } from 'harper'; import { pageSource } from './pageSource.js'; import { defaultRefreshInterval } from '../utils/constants.js'; diff --git a/utils/pageSource.js b/utils/pageSource.js index 1b33e80..86db030 100644 --- a/utils/pageSource.js +++ b/utils/pageSource.js @@ -35,7 +35,7 @@ import { performance } from 'node:perf_hooks'; import { createRequire } from 'node:module'; import { gzip, constants } from 'node:zlib'; import { promisify } from 'node:util'; -import { server } from 'harperdb'; +import { server } from 'harper'; import { filterContent } from './filter.js'; import { convertContent } from './converter.js'; import { createErrorPage, createMarkdownHeader } from './errorPage.js';