Change googleDriveAdapterHelper.js to use return statement

Reverted from object literal to function with return statement

Rationale:
- User requested explicit 'return' statement for clarity
- Function wrapper with 'return' is more conventional and readable
- Makes it clear the code returns an object when executed
- Matches common module pattern expectations

Structure:
```javascript
(function() {
  // Define all classes and functions
  class DocumentCountExceededError extends Error {...}
  function generateRequestId() {...}
  // ... all other functions

  // Return object with all exports
  return {
    DocumentCountExceededError,
    generateRequestId,
    // ... all other functions
  };
})();
```

Changes:
- Wrapped entire file in IIFE: (function() { ... })()
- Changed final expression from ({ ... }) to return { ... };
- Re-added 2-space indentation for content inside function
- Updated header comment: 'Returns' → 'Function that returns'

Benefits:
 Explicit return statement (clearer intent)
 Standard IIFE module pattern
 More conventional JavaScript style
 Easier to understand for new developers
 Same functionality as before

Testing:
✓ Syntax validated
✓ Server starts successfully
✓ Module loads: 'Loaded global functions: googleDriveAdapterHelper'
✓ All function calls work correctly
✓ Request handling functional

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-03-07 12:14:45 -06:00
parent c094d4d472
commit 07968927fc

View File

@@ -6,7 +6,7 @@
* *
* ARCHITECTURE: * ARCHITECTURE:
* - Loaded by server.js using vm.Script (same as proxy.js) * - Loaded by server.js using vm.Script (same as proxy.js)
* - Returns a single object containing all helper functions * - Function that returns a single object containing all helper functions
* - Injected into globalVariableContext for access by proxy.js * - Injected into globalVariableContext for access by proxy.js
* - NO IMPORTS - All dependencies provided via VM context * - NO IMPORTS - All dependencies provided via VM context
* *
@@ -17,296 +17,297 @@
* @returns {Object} Helpers object with all utility functions * @returns {Object} Helpers object with all utility functions
*/ */
/** (function() {
* Custom error for document count exceeding limit /**
*/ * Custom error for document count exceeding limit
class DocumentCountExceededError extends Error { */
constructor(count, limit) { class DocumentCountExceededError extends Error {
super(`Document count ${count} exceeds limit of ${limit}`); constructor(count, limit) {
this.name = "DocumentCountExceededError"; super(`Document count ${count} exceeds limit of ${limit}`);
this.count = count; this.name = "DocumentCountExceededError";
this.limit = limit; this.count = count;
this.statusCode = 413; this.limit = limit;
} this.statusCode = 413;
} }
// =============================================================================
// Utility Functions
// =============================================================================
/**
* Generate a unique request ID for tracing
* Uses UUID v4 for uniqueness
*
* @returns {string} Request ID in format: req_<uuid>
*/
function generateRequestId() {
return `req_${crypto.randomUUID()}`;
}
/**
* Validate document ID format
* Google Drive IDs are alphanumeric with hyphens and underscores
*
* @param {string} id - Document ID to validate
* @returns {boolean} True if valid
*/
function validateDocumentId(id) {
if (!id || typeof id !== "string") {
return false;
} }
// Google Drive IDs are typically 8-128 characters // =============================================================================
// Characters: a-z, A-Z, 0-9, -, _ // Utility Functions
const pattern = /^[a-zA-Z0-9_-]{8,128}$/; // =============================================================================
return pattern.test(id);
}
/** /**
* Validate document count against limit * Generate a unique request ID for tracing
* * Uses UUID v4 for uniqueness
* @param {number} count - Document count *
* @param {number} limit - Maximum allowed (default: 50000) * @returns {string} Request ID in format: req_<uuid>
* @throws {DocumentCountExceededError} If count > limit */
*/ function generateRequestId() {
function validateDocumentCount(count, limit = 50000) { return `req_${crypto.randomUUID()}`;
if (count > limit) {
throw new DocumentCountExceededError(count, limit);
}
}
// =============================================================================
// XML Utilities
// =============================================================================
/**
* Escape special XML characters
* Prevents XML injection and ensures valid XML output
*
* @param {string} str - String to escape
* @returns {string} Escaped string safe for XML
*/
function escapeXml(str) {
if (!str) return "";
return str
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&apos;");
}
// =============================================================================
// Error Mapping
// =============================================================================
/**
* Map Drive API error to HTTP status code and retry info
*
* Per specification:
* - 429: Rate limit - include Retry-After header
* - 503: Service unavailable - NO RETRY (fail immediately)
* - 401: Authentication failed
* - 500: Other errors
*
* @param {Error} error - Drive API error
* @returns {Object} { statusCode, retryAfter? }
*/
function mapDriveErrorToHttp(error) {
// Handle DocumentCountExceededError
if (error instanceof DocumentCountExceededError) {
return { statusCode: 413 };
} }
// Extract status code from Drive API error /**
const statusCode = error.response?.status || error.code || 500; * Validate document ID format
* Google Drive IDs are alphanumeric with hyphens and underscores
*
* @param {string} id - Document ID to validate
* @returns {boolean} True if valid
*/
function validateDocumentId(id) {
if (!id || typeof id !== "string") {
return false;
}
// Handle rate limiting (429) // Google Drive IDs are typically 8-128 characters
if (statusCode === 429) { // Characters: a-z, A-Z, 0-9, -, _
// Extract Retry-After from response headers if present const pattern = /^[a-zA-Z0-9_-]{8,128}$/;
const retryAfter = error.response?.headers?.["retry-after"]; return pattern.test(id);
const retryAfterSeconds = retryAfter ? parseInt(retryAfter, 10) : 60;
return {
statusCode: 429,
retryAfter: retryAfterSeconds,
};
} }
// Handle service unavailable (503) - NO RETRY per spec /**
if (statusCode === 503) { * Validate document count against limit
return { statusCode: 503 }; *
* @param {number} count - Document count
* @param {number} limit - Maximum allowed (default: 50000)
* @throws {DocumentCountExceededError} If count > limit
*/
function validateDocumentCount(count, limit = 50000) {
if (count > limit) {
throw new DocumentCountExceededError(count, limit);
}
} }
// Handle authentication errors // =============================================================================
if (statusCode === 401 || statusCode === 403) { // XML Utilities
return { statusCode: statusCode }; // =============================================================================
/**
* Escape special XML characters
* Prevents XML injection and ensures valid XML output
*
* @param {string} str - String to escape
* @returns {string} Escaped string safe for XML
*/
function escapeXml(str) {
if (!str) return "";
return str
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&apos;");
} }
// All other errors map to 500 // =============================================================================
return { statusCode: 500 }; // Error Mapping
} // =============================================================================
// ============================================================================= /**
// Sitemap Functions * Map Drive API error to HTTP status code and retry info
// ============================================================================= *
* Per specification:
* - 429: Rate limit - include Retry-After header
* - 503: Service unavailable - NO RETRY (fail immediately)
* - 401: Authentication failed
* - 500: Other errors
*
* @param {Error} error - Drive API error
* @returns {Object} { statusCode, retryAfter? }
*/
function mapDriveErrorToHttp(error) {
// Handle DocumentCountExceededError
if (error instanceof DocumentCountExceededError) {
return { statusCode: 413 };
}
/** // Extract status code from Drive API error
* Transform Drive document to sitemap entry const statusCode = error.response?.status || error.code || 500;
*
* Creates RESTful URL in format: {baseUrl}/documents/{documentId} // Handle rate limiting (429)
* Per specification clarification #2. if (statusCode === 429) {
* // Extract Retry-After from response headers if present
* @param {Object} document - Drive API document const retryAfter = error.response?.headers?.["retry-after"];
* @param {string} document.id - Document ID const retryAfterSeconds = retryAfter ? parseInt(retryAfter, 10) : 60;
* @param {string} document.modifiedTime - ISO 8601 timestamp
* @param {string} baseUrl - Base URL for the adapter return {
* @returns {Object} Sitemap entry { loc, lastmod } statusCode: 429,
*/ retryAfter: retryAfterSeconds,
function toSitemapEntry(document, baseUrl) { };
if (!document || !document.id) { }
console.error("Invalid document for sitemap entry", { document });
return null; // Handle service unavailable (503) - NO RETRY per spec
if (statusCode === 503) {
return { statusCode: 503 };
}
// Handle authentication errors
if (statusCode === 401 || statusCode === 403) {
return { statusCode: statusCode };
}
// All other errors map to 500
return { statusCode: 500 };
} }
// RESTful URL format: /documents/{documentId} // =============================================================================
const loc = `${baseUrl}/documents/${encodeURIComponent(document.id)}`; // Sitemap Functions
// =============================================================================
// Format lastmod as ISO 8601 date (YYYY-MM-DD) /**
let lastmod; * Transform Drive document to sitemap entry
if (document.modifiedTime) { *
try { * Creates RESTful URL in format: {baseUrl}/documents/{documentId}
const date = new Date(document.modifiedTime); * Per specification clarification #2.
lastmod = date.toISOString().split("T")[0]; // Extract YYYY-MM-DD *
} catch (error) { * @param {Object} document - Drive API document
console.error("Invalid modifiedTime for document", { * @param {string} document.id - Document ID
documentId: document.id, * @param {string} document.modifiedTime - ISO 8601 timestamp
modifiedTime: document.modifiedTime, * @param {string} baseUrl - Base URL for the adapter
}); * @returns {Object} Sitemap entry { loc, lastmod }
*/
function toSitemapEntry(document, baseUrl) {
if (!document || !document.id) {
console.error("Invalid document for sitemap entry", { document });
return null;
}
// RESTful URL format: /documents/{documentId}
const loc = `${baseUrl}/documents/${encodeURIComponent(document.id)}`;
// Format lastmod as ISO 8601 date (YYYY-MM-DD)
let lastmod;
if (document.modifiedTime) {
try {
const date = new Date(document.modifiedTime);
lastmod = date.toISOString().split("T")[0]; // Extract YYYY-MM-DD
} catch (error) {
console.error("Invalid modifiedTime for document", {
documentId: document.id,
modifiedTime: document.modifiedTime,
});
lastmod = new Date().toISOString().split("T")[0]; // Fallback to today
}
} else {
lastmod = new Date().toISOString().split("T")[0]; // Fallback to today lastmod = new Date().toISOString().split("T")[0]; // Fallback to today
} }
} else {
lastmod = new Date().toISOString().split("T")[0]; // Fallback to today return { loc, lastmod };
} }
return { loc, lastmod }; /**
} * Transform array of Drive documents to sitemap entries
*
* @param {Array<Object>} documents - Array of Drive API documents
* @param {string} baseUrl - Base URL for the adapter
* @returns {Array<Object>} Array of sitemap entries
*/
function transformDocumentsToSitemapEntries(documents, baseUrl) {
if (!Array.isArray(documents)) {
console.error("Documents must be an array", { documents });
return [];
}
/** return documents
* Transform array of Drive documents to sitemap entries .map((doc) => toSitemapEntry(doc, baseUrl))
* .filter((entry) => entry !== null);
* @param {Array<Object>} documents - Array of Drive API documents
* @param {string} baseUrl - Base URL for the adapter
* @returns {Array<Object>} Array of sitemap entries
*/
function transformDocumentsToSitemapEntries(documents, baseUrl) {
if (!Array.isArray(documents)) {
console.error("Documents must be an array", { documents });
return [];
} }
return documents /**
.map((doc) => toSitemapEntry(doc, baseUrl)) * Generate XML sitemap from sitemap entries
.filter((entry) => entry !== null); *
} * Handles empty sitemap (0 documents) case - returns valid XML with empty urlset.
*
* @param {Array<Object>} sitemapEntries - Array of { loc, lastmod } objects
* @returns {string} Complete XML sitemap string
*/
function generateSitemapXML(sitemapEntries) {
let xml = '<?xml version="1.0" encoding="UTF-8"?>\n';
xml += '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n';
/** // Handle empty sitemap - valid XML with no <url> elements
* Generate XML sitemap from sitemap entries if (!sitemapEntries || sitemapEntries.length === 0) {
* xml += "</urlset>";
* Handles empty sitemap (0 documents) case - returns valid XML with empty urlset. return xml;
* }
* @param {Array<Object>} sitemapEntries - Array of { loc, lastmod } objects
* @returns {string} Complete XML sitemap string for (const entry of sitemapEntries) {
*/ xml += " <url>\n";
function generateSitemapXML(sitemapEntries) { xml += ` <loc>${escapeXml(entry.loc)}</loc>\n`;
let xml = '<?xml version="1.0" encoding="UTF-8"?>\n'; xml += ` <lastmod>${escapeXml(entry.lastmod)}</lastmod>\n`;
xml += '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n'; xml += " </url>\n";
}
// Handle empty sitemap - valid XML with no <url> elements
if (!sitemapEntries || sitemapEntries.length === 0) {
xml += "</urlset>"; xml += "</urlset>";
return xml; return xml;
} }
for (const entry of sitemapEntries) { /**
xml += " <url>\n"; * Main sitemap generation function
xml += ` <loc>${escapeXml(entry.loc)}</loc>\n`; *
xml += ` <lastmod>${escapeXml(entry.lastmod)}</lastmod>\n`; * Combines document transformation and XML generation.
xml += " </url>\n"; *
* @param {Array<Object>} documents - Array of Drive API documents
* @param {string} baseUrl - Base URL for the adapter
* @returns {string} Complete XML sitemap
*/
function generateSitemap(documents, baseUrl) {
const entries = transformDocumentsToSitemapEntries(documents, baseUrl);
return generateSitemapXML(entries);
} }
xml += "</urlset>"; // =============================================================================
// Route Parsing
// =============================================================================
return xml; /**
} * Parse route from request
* @param {string} method - HTTP method
* @param {string} url - Request URL
* @returns {Object} Route info or error
*/
function parseRoute(method, url) {
if (method !== "GET") {
return { route: null, error: "Method not allowed", statusCode: 405 };
}
/** const urlObj = new URL(url, "http://localhost");
* Main sitemap generation function const path = urlObj.pathname;
*
* Combines document transformation and XML generation.
*
* @param {Array<Object>} documents - Array of Drive API documents
* @param {string} baseUrl - Base URL for the adapter
* @returns {string} Complete XML sitemap
*/
function generateSitemap(documents, baseUrl) {
const entries = transformDocumentsToSitemapEntries(documents, baseUrl);
return generateSitemapXML(entries);
}
// ============================================================================= // Match any path containing 'sitemap.xml'
// Route Parsing if (path.includes("sitemap.xml")) {
// ============================================================================= return { route: "sitemap" };
}
/** // All other paths return 404
* Parse route from request return { route: null, error: "Not found", statusCode: 404 };
* @param {string} method - HTTP method
* @param {string} url - Request URL
* @returns {Object} Route info or error
*/
function parseRoute(method, url) {
if (method !== "GET") {
return { route: null, error: "Method not allowed", statusCode: 405 };
} }
// =============================================================================
// Return helpers object with all functions
// =============================================================================
const urlObj = new URL(url, "http://localhost"); return {
const path = urlObj.pathname; // Error classes
DocumentCountExceededError,
// Match any path containing 'sitemap.xml' // Utilities
if (path.includes("sitemap.xml")) { generateRequestId,
return { route: "sitemap" }; validateDocumentId,
} validateDocumentCount,
// All other paths return 404 // XML
return { route: null, error: "Not found", statusCode: 404 }; escapeXml,
}
// ============================================================================= // Error mapping
// Return helpers object with all functions mapDriveErrorToHttp,
// =============================================================================
({ // Sitemap
// Error classes toSitemapEntry,
DocumentCountExceededError, transformDocumentsToSitemapEntries,
generateSitemapXML,
generateSitemap,
// Utilities // Routing
generateRequestId, parseRoute,
validateDocumentId, };
validateDocumentCount, })();
// XML
escapeXml,
// Error mapping
mapDriveErrorToHttp,
// Sitemap
toSitemapEntry,
transformDocumentsToSitemapEntries,
generateSitemapXML,
generateSitemap,
// Routing
parseRoute,
});