UNPKG

qmemory

Version:

A comprehensive production-ready Node.js utility library with MongoDB document operations, user ownership enforcement, Express.js HTTP utilities, environment-aware logging, and in-memory storage. Features 96%+ test coverage with comprehensive error handli

153 lines (137 loc) 3.9 kB
const { Storage, File } = require("@google-cloud/storage"); const { randomUUID } = require("crypto"); const REPLIT_SIDECAR_ENDPOINT = "http://127.0.0.1:1106"; // The object storage client is used to interact with the object storage service. const objectStorageClient = new Storage({ credentials: { audience: "replit", subject_token_type: "access_token", token_url: `${REPLIT_SIDECAR_ENDPOINT}/token`, type: "external_account", credential_source: { url: `${REPLIT_SIDECAR_ENDPOINT}/credential`, format: { type: "json", subject_token_field_name: "access_token", }, }, universe_domain: "googleapis.com", }, projectId: "", }); class ObjectNotFoundError extends Error { constructor() { super("Object not found"); this.name = "ObjectNotFoundError"; Object.setPrototypeOf(this, ObjectNotFoundError.prototype); } } // The object storage service is used to interact with the object storage service. class ObjectStorageService { constructor() { this.objectStorageClient = objectStorageClient; } // Gets the public object search paths. getPublicObjectSearchPaths() { const pathsStr = process.env.PUBLIC_OBJECT_SEARCH_PATHS || ""; const paths = Array.from( new Set( pathsStr .split(",") .map((path) => path.trim()) .filter((path) => path.length > 0) ) ); if (paths.length === 0) { throw new Error( "PUBLIC_OBJECT_SEARCH_PATHS not set. Create a bucket in 'Object Storage' " + "tool and set PUBLIC_OBJECT_SEARCH_PATHS env var (comma-separated paths)." ); } return paths; } // Gets the private object directory. getPrivateObjectDir() { const dir = process.env.PRIVATE_OBJECT_DIR || ""; if (!dir) { throw new Error( "PRIVATE_OBJECT_DIR not set. Create a bucket in 'Object Storage' " + "tool and set PRIVATE_OBJECT_DIR env var." ); } return dir; } // Gets the upload URL for an object entity. async getObjectEntityUploadURL() { const privateObjectDir = this.getPrivateObjectDir(); if (!privateObjectDir) { throw new Error( "PRIVATE_OBJECT_DIR not set. Create a bucket in 'Object Storage' " + "tool and set PRIVATE_OBJECT_DIR env var." ); } const objectId = randomUUID(); const fullPath = `${privateObjectDir}/uploads/${objectId}`; const { bucketName, objectName } = parseObjectPath(fullPath); // Sign URL for PUT method with TTL return signObjectURL({ bucketName, objectName, method: "PUT", ttlSec: 900, }); } } function parseObjectPath(path) { if (!path.startsWith("/")) { path = `/${path}`; } const pathParts = path.split("/"); if (pathParts.length < 3) { throw new Error("Invalid path: must contain at least a bucket name"); } const bucketName = pathParts[1]; const objectName = pathParts.slice(2).join("/"); return { bucketName, objectName, }; } async function signObjectURL({ bucketName, objectName, method, ttlSec, }) { const request = { bucket_name: bucketName, object_name: objectName, method, expires_at: new Date(Date.now() + ttlSec * 1000).toISOString(), }; const response = await fetch( `${REPLIT_SIDECAR_ENDPOINT}/object-storage/signed-object-url`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(request), } ); if (!response.ok) { throw new Error( `Failed to sign object URL, errorcode: ${response.status}, ` + `make sure you're running on Replit` ); } const { signed_url: signedURL } = await response.json(); return signedURL; } module.exports = { ObjectStorageService, ObjectNotFoundError, objectStorageClient, parseObjectPath, signObjectURL };