#include "dbal/client.hpp" #include #include #include #include #include namespace dbal { // In-memory store for mock implementation struct InMemoryStore { std::map users; std::map pages; std::map page_slugs; // slug -> id mapping std::map workflows; std::map workflow_names; // name -> id mapping std::map sessions; std::map session_tokens; // token -> id mapping std::map lua_scripts; std::map lua_script_names; // name -> id mapping std::map packages; std::map package_keys; // name@version -> id mapping int user_counter = 0; int page_counter = 0; int workflow_counter = 0; int session_counter = 0; int lua_script_counter = 0; int package_counter = 0; }; static InMemoryStore& getStore() { static InMemoryStore store; return store; } // Validation helpers static bool isValidEmail(const std::string& email) { static const std::regex email_pattern(R"([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})"); return std::regex_match(email, email_pattern); } static bool isValidUsername(const std::string& username) { if (username.empty() || username.length() > 50) return false; static const std::regex username_pattern(R"([a-zA-Z0-9_-]+)"); return std::regex_match(username, username_pattern); } static bool isValidSlug(const std::string& slug) { if (slug.empty() || slug.length() > 100) return false; static const std::regex slug_pattern(R"([a-z0-9-]+)"); return std::regex_match(slug, slug_pattern); } static bool isValidWorkflowName(const std::string& name) { return !name.empty() && name.length() <= 255; } static bool isValidWorkflowTrigger(const std::string& trigger) { static const std::array allowed = {"manual", "schedule", "event", "webhook"}; return std::find(allowed.begin(), allowed.end(), trigger) != allowed.end(); } static bool isValidLuaScriptName(const std::string& name) { return !name.empty() && name.length() <= 255; } static bool isValidLuaScriptCode(const std::string& code) { return !code.empty(); } static bool isValidLuaTimeout(int timeout_ms) { return timeout_ms >= 100 && timeout_ms <= 30000; } static bool isValidPackageName(const std::string& name) { return !name.empty() && name.length() <= 255; } static bool isValidSemver(const std::string& version) { static const std::regex semver_pattern(R"(^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$)"); return std::regex_match(version, semver_pattern); } static std::string packageKey(const std::string& name, const std::string& version) { return name + "@" + version; } static std::string generateId(const std::string& prefix, int counter) { char buffer[64]; snprintf(buffer, sizeof(buffer), "%s_%08d", prefix.c_str(), counter); return std::string(buffer); } Client::Client(const ClientConfig& config) : config_(config) { // Validate configuration if (config.adapter.empty()) { throw std::invalid_argument("Adapter type must be specified"); } if (config.database_url.empty()) { throw std::invalid_argument("Database URL must be specified"); } } Client::~Client() { close(); } Result Client::createUser(const CreateUserInput& input) { // Validation if (!isValidUsername(input.username)) { return Error::validationError("Invalid username format (alphanumeric, underscore, hyphen only)"); } if (!isValidEmail(input.email)) { return Error::validationError("Invalid email format"); } auto& store = getStore(); // Check for duplicate username for (const auto& [id, user] : store.users) { if (user.username == input.username) { return Error::conflict("Username already exists: " + input.username); } if (user.email == input.email) { return Error::conflict("Email already exists: " + input.email); } } // Create user User user; user.id = generateId("user", ++store.user_counter); user.username = input.username; user.email = input.email; user.role = input.role; user.created_at = std::chrono::system_clock::now(); user.updated_at = user.created_at; store.users[user.id] = user; return Result(user); } Result Client::getUser(const std::string& id) { if (id.empty()) { return Error::validationError("User ID cannot be empty"); } auto& store = getStore(); auto it = store.users.find(id); if (it == store.users.end()) { return Error::notFound("User not found: " + id); } return Result(it->second); } Result Client::updateUser(const std::string& id, const UpdateUserInput& input) { if (id.empty()) { return Error::validationError("User ID cannot be empty"); } auto& store = getStore(); auto it = store.users.find(id); if (it == store.users.end()) { return Error::notFound("User not found: " + id); } User& user = it->second; // Validate and check conflicts if (input.username.has_value()) { if (!isValidUsername(input.username.value())) { return Error::validationError("Invalid username format"); } // Check for username conflict for (const auto& [uid, u] : store.users) { if (uid != id && u.username == input.username.value()) { return Error::conflict("Username already exists: " + input.username.value()); } } user.username = input.username.value(); } if (input.email.has_value()) { if (!isValidEmail(input.email.value())) { return Error::validationError("Invalid email format"); } // Check for email conflict for (const auto& [uid, u] : store.users) { if (uid != id && u.email == input.email.value()) { return Error::conflict("Email already exists: " + input.email.value()); } } user.email = input.email.value(); } if (input.role.has_value()) { user.role = input.role.value(); } user.updated_at = std::chrono::system_clock::now(); return Result(user); } Result Client::deleteUser(const std::string& id) { if (id.empty()) { return Error::validationError("User ID cannot be empty"); } auto& store = getStore(); auto it = store.users.find(id); if (it == store.users.end()) { return Error::notFound("User not found: " + id); } store.users.erase(it); return Result(true); } Result> Client::listUsers(const ListOptions& options) { auto& store = getStore(); std::vector users; for (const auto& [id, user] : store.users) { // Apply filters if provided bool matches = true; if (options.filter.find("role") != options.filter.end()) { std::string role_str = options.filter.at("role"); // Simple role filtering if (role_str == "admin" && user.role != UserRole::Admin) matches = false; if (role_str == "user" && user.role != UserRole::User) matches = false; } if (matches) { users.push_back(user); } } // Apply sorting if (options.sort.find("username") != options.sort.end()) { std::sort(users.begin(), users.end(), [](const User& a, const User& b) { return a.username < b.username; }); } // Apply pagination int start = (options.page - 1) * options.limit; int end = std::min(start + options.limit, static_cast(users.size())); if (start < static_cast(users.size())) { return Result>(std::vector(users.begin() + start, users.begin() + end)); } return Result>(std::vector()); } Result Client::batchCreateUsers(const std::vector& inputs) { if (inputs.empty()) { return Result(0); } std::vector created_ids; for (const auto& input : inputs) { auto result = createUser(input); if (result.isError()) { auto& store = getStore(); for (const auto& id : created_ids) { store.users.erase(id); } return result.error(); } created_ids.push_back(result.value().id); } return Result(static_cast(created_ids.size())); } Result Client::batchUpdateUsers(const std::vector& updates) { if (updates.empty()) { return Result(0); } int updated = 0; for (const auto& item : updates) { auto result = updateUser(item.id, item.data); if (result.isError()) { return result.error(); } updated++; } return Result(updated); } Result Client::batchDeleteUsers(const std::vector& ids) { if (ids.empty()) { return Result(0); } int deleted = 0; for (const auto& id : ids) { auto result = deleteUser(id); if (result.isError()) { return result.error(); } deleted++; } return Result(deleted); } Result Client::createPage(const CreatePageInput& input) { // Validation if (!isValidSlug(input.slug)) { return Error::validationError("Invalid slug format (lowercase, alphanumeric, hyphens only)"); } if (input.title.empty() || input.title.length() > 200) { return Error::validationError("Title must be between 1 and 200 characters"); } if (input.level < 0 || input.level > 5) { return Error::validationError("Level must be between 0 and 5"); } auto& store = getStore(); // Check for duplicate slug if (store.page_slugs.find(input.slug) != store.page_slugs.end()) { return Error::conflict("Page with slug already exists: " + input.slug); } // Create page PageView page; page.id = generateId("page", ++store.page_counter); page.slug = input.slug; page.title = input.title; page.description = input.description; page.level = input.level; page.layout = input.layout; page.is_active = input.is_active; page.created_at = std::chrono::system_clock::now(); page.updated_at = page.created_at; store.pages[page.id] = page; store.page_slugs[page.slug] = page.id; return Result(page); } Result Client::getPage(const std::string& id) { if (id.empty()) { return Error::validationError("Page ID cannot be empty"); } auto& store = getStore(); auto it = store.pages.find(id); if (it == store.pages.end()) { return Error::notFound("Page not found: " + id); } return Result(it->second); } Result Client::getPageBySlug(const std::string& slug) { if (slug.empty()) { return Error::validationError("Slug cannot be empty"); } auto& store = getStore(); auto it = store.page_slugs.find(slug); if (it == store.page_slugs.end()) { return Error::notFound("Page not found with slug: " + slug); } return getPage(it->second); } Result Client::updatePage(const std::string& id, const UpdatePageInput& input) { if (id.empty()) { return Error::validationError("Page ID cannot be empty"); } auto& store = getStore(); auto it = store.pages.find(id); if (it == store.pages.end()) { return Error::notFound("Page not found: " + id); } PageView& page = it->second; std::string old_slug = page.slug; // Validate and update fields if (input.slug.has_value()) { if (!isValidSlug(input.slug.value())) { return Error::validationError("Invalid slug format"); } // Check for slug conflict auto slug_it = store.page_slugs.find(input.slug.value()); if (slug_it != store.page_slugs.end() && slug_it->second != id) { return Error::conflict("Slug already exists: " + input.slug.value()); } // Update slug mapping store.page_slugs.erase(old_slug); store.page_slugs[input.slug.value()] = id; page.slug = input.slug.value(); } if (input.title.has_value()) { if (input.title.value().empty() || input.title.value().length() > 200) { return Error::validationError("Title must be between 1 and 200 characters"); } page.title = input.title.value(); } if (input.description.has_value()) { page.description = input.description.value(); } if (input.level.has_value()) { if (input.level.value() < 0 || input.level.value() > 5) { return Error::validationError("Level must be between 0 and 5"); } page.level = input.level.value(); } if (input.layout.has_value()) { page.layout = input.layout.value(); } if (input.is_active.has_value()) { page.is_active = input.is_active.value(); } page.updated_at = std::chrono::system_clock::now(); return Result(page); } Result Client::deletePage(const std::string& id) { if (id.empty()) { return Error::validationError("Page ID cannot be empty"); } auto& store = getStore(); auto it = store.pages.find(id); if (it == store.pages.end()) { return Error::notFound("Page not found: " + id); } // Remove slug mapping store.page_slugs.erase(it->second.slug); store.pages.erase(it); return Result(true); } Result> Client::listPages(const ListOptions& options) { auto& store = getStore(); std::vector pages; for (const auto& [id, page] : store.pages) { // Apply filters bool matches = true; if (options.filter.find("is_active") != options.filter.end()) { bool filter_active = options.filter.at("is_active") == "true"; if (page.is_active != filter_active) matches = false; } if (options.filter.find("level") != options.filter.end()) { int filter_level = std::stoi(options.filter.at("level")); if (page.level != filter_level) matches = false; } if (matches) { pages.push_back(page); } } // Apply sorting if (options.sort.find("title") != options.sort.end()) { std::sort(pages.begin(), pages.end(), [](const PageView& a, const PageView& b) { return a.title < b.title; }); } else if (options.sort.find("created_at") != options.sort.end()) { std::sort(pages.begin(), pages.end(), [](const PageView& a, const PageView& b) { return a.created_at < b.created_at; }); } // Apply pagination int start = (options.page - 1) * options.limit; int end = std::min(start + options.limit, static_cast(pages.size())); if (start < static_cast(pages.size())) { return Result>(std::vector(pages.begin() + start, pages.begin() + end)); } return Result>(std::vector()); } Result Client::createWorkflow(const CreateWorkflowInput& input) { if (!isValidWorkflowName(input.name)) { return Error::validationError("Workflow name must be 1-255 characters"); } if (!isValidWorkflowTrigger(input.trigger)) { return Error::validationError("Trigger must be one of manual, schedule, event, webhook"); } if (input.created_by.empty()) { return Error::validationError("created_by is required"); } auto& store = getStore(); if (store.workflow_names.find(input.name) != store.workflow_names.end()) { return Error::conflict("Workflow name already exists: " + input.name); } Workflow workflow; workflow.id = generateId("workflow", ++store.workflow_counter); workflow.name = input.name; workflow.description = input.description; workflow.trigger = input.trigger; workflow.trigger_config = input.trigger_config; workflow.steps = input.steps; workflow.is_active = input.is_active; workflow.created_by = input.created_by; workflow.created_at = std::chrono::system_clock::now(); workflow.updated_at = workflow.created_at; store.workflows[workflow.id] = workflow; store.workflow_names[workflow.name] = workflow.id; return Result(workflow); } Result Client::getWorkflow(const std::string& id) { if (id.empty()) { return Error::validationError("Workflow ID cannot be empty"); } auto& store = getStore(); auto it = store.workflows.find(id); if (it == store.workflows.end()) { return Error::notFound("Workflow not found: " + id); } return Result(it->second); } Result Client::updateWorkflow(const std::string& id, const UpdateWorkflowInput& input) { if (id.empty()) { return Error::validationError("Workflow ID cannot be empty"); } auto& store = getStore(); auto it = store.workflows.find(id); if (it == store.workflows.end()) { return Error::notFound("Workflow not found: " + id); } Workflow& workflow = it->second; std::string old_name = workflow.name; if (input.name.has_value()) { if (!isValidWorkflowName(input.name.value())) { return Error::validationError("Workflow name must be 1-255 characters"); } auto name_it = store.workflow_names.find(input.name.value()); if (name_it != store.workflow_names.end() && name_it->second != id) { return Error::conflict("Workflow name already exists: " + input.name.value()); } store.workflow_names.erase(old_name); store.workflow_names[input.name.value()] = id; workflow.name = input.name.value(); } if (input.description.has_value()) { workflow.description = input.description.value(); } if (input.trigger.has_value()) { if (!isValidWorkflowTrigger(input.trigger.value())) { return Error::validationError("Trigger must be one of manual, schedule, event, webhook"); } workflow.trigger = input.trigger.value(); } if (input.trigger_config.has_value()) { workflow.trigger_config = input.trigger_config.value(); } if (input.steps.has_value()) { workflow.steps = input.steps.value(); } if (input.is_active.has_value()) { workflow.is_active = input.is_active.value(); } if (input.created_by.has_value()) { if (input.created_by.value().empty()) { return Error::validationError("created_by is required"); } workflow.created_by = input.created_by.value(); } workflow.updated_at = std::chrono::system_clock::now(); return Result(workflow); } Result Client::deleteWorkflow(const std::string& id) { if (id.empty()) { return Error::validationError("Workflow ID cannot be empty"); } auto& store = getStore(); auto it = store.workflows.find(id); if (it == store.workflows.end()) { return Error::notFound("Workflow not found: " + id); } store.workflow_names.erase(it->second.name); store.workflows.erase(it); return Result(true); } Result> Client::listWorkflows(const ListOptions& options) { auto& store = getStore(); std::vector workflows; for (const auto& [id, workflow] : store.workflows) { bool matches = true; if (options.filter.find("is_active") != options.filter.end()) { bool filter_active = options.filter.at("is_active") == "true"; if (workflow.is_active != filter_active) matches = false; } if (options.filter.find("trigger") != options.filter.end()) { if (workflow.trigger != options.filter.at("trigger")) matches = false; } if (options.filter.find("created_by") != options.filter.end()) { if (workflow.created_by != options.filter.at("created_by")) matches = false; } if (matches) { workflows.push_back(workflow); } } if (options.sort.find("name") != options.sort.end()) { std::sort(workflows.begin(), workflows.end(), [](const Workflow& a, const Workflow& b) { return a.name < b.name; }); } else if (options.sort.find("created_at") != options.sort.end()) { std::sort(workflows.begin(), workflows.end(), [](const Workflow& a, const Workflow& b) { return a.created_at < b.created_at; }); } int start = (options.page - 1) * options.limit; int end = std::min(start + options.limit, static_cast(workflows.size())); if (start < static_cast(workflows.size())) { return Result>(std::vector(workflows.begin() + start, workflows.begin() + end)); } return Result>(std::vector()); } Result Client::createSession(const CreateSessionInput& input) { if (input.user_id.empty()) { return Error::validationError("user_id is required"); } if (input.token.empty()) { return Error::validationError("token is required"); } auto& store = getStore(); if (store.users.find(input.user_id) == store.users.end()) { return Error::validationError("User not found: " + input.user_id); } if (store.session_tokens.find(input.token) != store.session_tokens.end()) { return Error::conflict("Session token already exists: " + input.token); } Session session; session.id = generateId("session", ++store.session_counter); session.user_id = input.user_id; session.token = input.token; session.expires_at = input.expires_at; session.created_at = std::chrono::system_clock::now(); session.last_activity = session.created_at; store.sessions[session.id] = session; store.session_tokens[session.token] = session.id; return Result(session); } Result Client::getSession(const std::string& id) { if (id.empty()) { return Error::validationError("Session ID cannot be empty"); } auto& store = getStore(); auto it = store.sessions.find(id); if (it == store.sessions.end()) { return Error::notFound("Session not found: " + id); } auto now = std::chrono::system_clock::now(); if (it->second.expires_at <= now) { store.session_tokens.erase(it->second.token); store.sessions.erase(it); return Error::notFound("Session expired: " + id); } return Result(it->second); } Result Client::updateSession(const std::string& id, const UpdateSessionInput& input) { if (id.empty()) { return Error::validationError("Session ID cannot be empty"); } auto& store = getStore(); auto it = store.sessions.find(id); if (it == store.sessions.end()) { return Error::notFound("Session not found: " + id); } Session& session = it->second; if (input.user_id.has_value()) { if (input.user_id.value().empty()) { return Error::validationError("user_id is required"); } if (store.users.find(input.user_id.value()) == store.users.end()) { return Error::validationError("User not found: " + input.user_id.value()); } session.user_id = input.user_id.value(); } if (input.token.has_value()) { if (input.token.value().empty()) { return Error::validationError("token is required"); } auto token_it = store.session_tokens.find(input.token.value()); if (token_it != store.session_tokens.end() && token_it->second != id) { return Error::conflict("Session token already exists: " + input.token.value()); } store.session_tokens.erase(session.token); store.session_tokens[input.token.value()] = id; session.token = input.token.value(); } if (input.expires_at.has_value()) { session.expires_at = input.expires_at.value(); } if (input.last_activity.has_value()) { session.last_activity = input.last_activity.value(); } return Result(session); } Result Client::deleteSession(const std::string& id) { if (id.empty()) { return Error::validationError("Session ID cannot be empty"); } auto& store = getStore(); auto it = store.sessions.find(id); if (it == store.sessions.end()) { return Error::notFound("Session not found: " + id); } store.session_tokens.erase(it->second.token); store.sessions.erase(it); return Result(true); } Result> Client::listSessions(const ListOptions& options) { auto& store = getStore(); auto now = std::chrono::system_clock::now(); std::vector expired_sessions; for (const auto& [id, session] : store.sessions) { if (session.expires_at <= now) { expired_sessions.push_back(id); } } for (const auto& id : expired_sessions) { auto expired_it = store.sessions.find(id); if (expired_it == store.sessions.end()) { continue; } store.session_tokens.erase(expired_it->second.token); store.sessions.erase(expired_it); } std::vector sessions; for (const auto& [id, session] : store.sessions) { bool matches = true; if (options.filter.find("user_id") != options.filter.end()) { if (session.user_id != options.filter.at("user_id")) matches = false; } if (options.filter.find("token") != options.filter.end()) { if (session.token != options.filter.at("token")) matches = false; } if (matches) { sessions.push_back(session); } } if (options.sort.find("created_at") != options.sort.end()) { std::sort(sessions.begin(), sessions.end(), [](const Session& a, const Session& b) { return a.created_at < b.created_at; }); } else if (options.sort.find("expires_at") != options.sort.end()) { std::sort(sessions.begin(), sessions.end(), [](const Session& a, const Session& b) { return a.expires_at < b.expires_at; }); } int start = (options.page - 1) * options.limit; int end = std::min(start + options.limit, static_cast(sessions.size())); if (start < static_cast(sessions.size())) { return Result>(std::vector(sessions.begin() + start, sessions.begin() + end)); } return Result>(std::vector()); } Result Client::createLuaScript(const CreateLuaScriptInput& input) { if (!isValidLuaScriptName(input.name)) { return Error::validationError("Lua script name must be 1-255 characters"); } if (!isValidLuaScriptCode(input.code)) { return Error::validationError("Lua script code must be a non-empty string"); } if (!isValidLuaTimeout(input.timeout_ms)) { return Error::validationError("Timeout must be between 100 and 30000 ms"); } if (input.created_by.empty()) { return Error::validationError("created_by is required"); } for (const auto& entry : input.allowed_globals) { if (entry.empty()) { return Error::validationError("allowed_globals must contain non-empty strings"); } } auto& store = getStore(); if (store.lua_script_names.find(input.name) != store.lua_script_names.end()) { return Error::conflict("Lua script name already exists: " + input.name); } LuaScript script; script.id = generateId("lua", ++store.lua_script_counter); script.name = input.name; script.description = input.description; script.code = input.code; script.is_sandboxed = input.is_sandboxed; script.allowed_globals = input.allowed_globals; script.timeout_ms = input.timeout_ms; script.created_by = input.created_by; script.created_at = std::chrono::system_clock::now(); script.updated_at = script.created_at; store.lua_scripts[script.id] = script; store.lua_script_names[script.name] = script.id; return Result(script); } Result Client::getLuaScript(const std::string& id) { if (id.empty()) { return Error::validationError("Lua script ID cannot be empty"); } auto& store = getStore(); auto it = store.lua_scripts.find(id); if (it == store.lua_scripts.end()) { return Error::notFound("Lua script not found: " + id); } return Result(it->second); } Result Client::updateLuaScript(const std::string& id, const UpdateLuaScriptInput& input) { if (id.empty()) { return Error::validationError("Lua script ID cannot be empty"); } auto& store = getStore(); auto it = store.lua_scripts.find(id); if (it == store.lua_scripts.end()) { return Error::notFound("Lua script not found: " + id); } LuaScript& script = it->second; std::string old_name = script.name; if (input.name.has_value()) { if (!isValidLuaScriptName(input.name.value())) { return Error::validationError("Lua script name must be 1-255 characters"); } auto name_it = store.lua_script_names.find(input.name.value()); if (name_it != store.lua_script_names.end() && name_it->second != id) { return Error::conflict("Lua script name already exists: " + input.name.value()); } store.lua_script_names.erase(old_name); store.lua_script_names[input.name.value()] = id; script.name = input.name.value(); } if (input.description.has_value()) { script.description = input.description.value(); } if (input.code.has_value()) { if (!isValidLuaScriptCode(input.code.value())) { return Error::validationError("Lua script code must be a non-empty string"); } script.code = input.code.value(); } if (input.is_sandboxed.has_value()) { script.is_sandboxed = input.is_sandboxed.value(); } if (input.allowed_globals.has_value()) { for (const auto& entry : input.allowed_globals.value()) { if (entry.empty()) { return Error::validationError("allowed_globals must contain non-empty strings"); } } script.allowed_globals = input.allowed_globals.value(); } if (input.timeout_ms.has_value()) { if (!isValidLuaTimeout(input.timeout_ms.value())) { return Error::validationError("Timeout must be between 100 and 30000 ms"); } script.timeout_ms = input.timeout_ms.value(); } if (input.created_by.has_value()) { if (input.created_by.value().empty()) { return Error::validationError("created_by is required"); } script.created_by = input.created_by.value(); } script.updated_at = std::chrono::system_clock::now(); return Result(script); } Result Client::deleteLuaScript(const std::string& id) { if (id.empty()) { return Error::validationError("Lua script ID cannot be empty"); } auto& store = getStore(); auto it = store.lua_scripts.find(id); if (it == store.lua_scripts.end()) { return Error::notFound("Lua script not found: " + id); } store.lua_script_names.erase(it->second.name); store.lua_scripts.erase(it); return Result(true); } Result> Client::listLuaScripts(const ListOptions& options) { auto& store = getStore(); std::vector scripts; for (const auto& [id, script] : store.lua_scripts) { bool matches = true; if (options.filter.find("created_by") != options.filter.end()) { if (script.created_by != options.filter.at("created_by")) matches = false; } if (options.filter.find("is_sandboxed") != options.filter.end()) { bool filter_sandboxed = options.filter.at("is_sandboxed") == "true"; if (script.is_sandboxed != filter_sandboxed) matches = false; } if (matches) { scripts.push_back(script); } } if (options.sort.find("name") != options.sort.end()) { std::sort(scripts.begin(), scripts.end(), [](const LuaScript& a, const LuaScript& b) { return a.name < b.name; }); } else if (options.sort.find("created_at") != options.sort.end()) { std::sort(scripts.begin(), scripts.end(), [](const LuaScript& a, const LuaScript& b) { return a.created_at < b.created_at; }); } int start = (options.page - 1) * options.limit; int end = std::min(start + options.limit, static_cast(scripts.size())); if (start < static_cast(scripts.size())) { return Result>(std::vector(scripts.begin() + start, scripts.begin() + end)); } return Result>(std::vector()); } Result Client::createPackage(const CreatePackageInput& input) { if (!isValidPackageName(input.name)) { return Error::validationError("Package name must be 1-255 characters"); } if (!isValidSemver(input.version)) { return Error::validationError("Version must be valid semver"); } if (input.author.empty()) { return Error::validationError("author is required"); } auto& store = getStore(); std::string key = packageKey(input.name, input.version); if (store.package_keys.find(key) != store.package_keys.end()) { return Error::conflict("Package name+version already exists: " + key); } Package package; package.id = generateId("package", ++store.package_counter); package.name = input.name; package.version = input.version; package.description = input.description; package.author = input.author; package.manifest = input.manifest; package.is_installed = input.is_installed; package.installed_at = input.installed_at; package.installed_by = input.installed_by; package.created_at = std::chrono::system_clock::now(); package.updated_at = package.created_at; store.packages[package.id] = package; store.package_keys[key] = package.id; return Result(package); } Result Client::getPackage(const std::string& id) { if (id.empty()) { return Error::validationError("Package ID cannot be empty"); } auto& store = getStore(); auto it = store.packages.find(id); if (it == store.packages.end()) { return Error::notFound("Package not found: " + id); } return Result(it->second); } Result Client::updatePackage(const std::string& id, const UpdatePackageInput& input) { if (id.empty()) { return Error::validationError("Package ID cannot be empty"); } auto& store = getStore(); auto it = store.packages.find(id); if (it == store.packages.end()) { return Error::notFound("Package not found: " + id); } Package& package = it->second; std::string next_name = input.name.value_or(package.name); std::string next_version = input.version.value_or(package.version); if (!isValidPackageName(next_name)) { return Error::validationError("Package name must be 1-255 characters"); } if (!isValidSemver(next_version)) { return Error::validationError("Version must be valid semver"); } std::string current_key = packageKey(package.name, package.version); std::string next_key = packageKey(next_name, next_version); if (next_key != current_key) { auto key_it = store.package_keys.find(next_key); if (key_it != store.package_keys.end() && key_it->second != id) { return Error::conflict("Package name+version already exists: " + next_key); } store.package_keys.erase(current_key); store.package_keys[next_key] = id; } package.name = next_name; package.version = next_version; if (input.description.has_value()) { package.description = input.description.value(); } if (input.author.has_value()) { if (input.author.value().empty()) { return Error::validationError("author is required"); } package.author = input.author.value(); } if (input.manifest.has_value()) { package.manifest = input.manifest.value(); } if (input.is_installed.has_value()) { package.is_installed = input.is_installed.value(); } if (input.installed_at.has_value()) { package.installed_at = input.installed_at.value(); } if (input.installed_by.has_value()) { if (input.installed_by.value().empty()) { return Error::validationError("installed_by is required"); } package.installed_by = input.installed_by.value(); } package.updated_at = std::chrono::system_clock::now(); return Result(package); } Result Client::deletePackage(const std::string& id) { if (id.empty()) { return Error::validationError("Package ID cannot be empty"); } auto& store = getStore(); auto it = store.packages.find(id); if (it == store.packages.end()) { return Error::notFound("Package not found: " + id); } store.package_keys.erase(packageKey(it->second.name, it->second.version)); store.packages.erase(it); return Result(true); } Result> Client::listPackages(const ListOptions& options) { auto& store = getStore(); std::vector packages; for (const auto& [id, package] : store.packages) { bool matches = true; if (options.filter.find("name") != options.filter.end()) { if (package.name != options.filter.at("name")) matches = false; } if (options.filter.find("version") != options.filter.end()) { if (package.version != options.filter.at("version")) matches = false; } if (options.filter.find("author") != options.filter.end()) { if (package.author != options.filter.at("author")) matches = false; } if (options.filter.find("is_installed") != options.filter.end()) { bool filter_installed = options.filter.at("is_installed") == "true"; if (package.is_installed != filter_installed) matches = false; } if (matches) { packages.push_back(package); } } if (options.sort.find("name") != options.sort.end()) { std::sort(packages.begin(), packages.end(), [](const Package& a, const Package& b) { return a.name < b.name; }); } else if (options.sort.find("created_at") != options.sort.end()) { std::sort(packages.begin(), packages.end(), [](const Package& a, const Package& b) { return a.created_at < b.created_at; }); } int start = (options.page - 1) * options.limit; int end = std::min(start + options.limit, static_cast(packages.size())); if (start < static_cast(packages.size())) { return Result>(std::vector(packages.begin() + start, packages.begin() + end)); } return Result>(std::vector()); } Result Client::batchCreatePackages(const std::vector& inputs) { if (inputs.empty()) { return Result(0); } std::vector created_ids; for (const auto& input : inputs) { auto result = createPackage(input); if (result.isError()) { auto& store = getStore(); for (const auto& id : created_ids) { auto it = store.packages.find(id); if (it != store.packages.end()) { store.package_keys.erase(packageKey(it->second.name, it->second.version)); store.packages.erase(it); } } return result.error(); } created_ids.push_back(result.value().id); } return Result(static_cast(created_ids.size())); } Result Client::batchUpdatePackages(const std::vector& updates) { if (updates.empty()) { return Result(0); } int updated = 0; for (const auto& item : updates) { auto result = updatePackage(item.id, item.data); if (result.isError()) { return result.error(); } updated++; } return Result(updated); } Result Client::batchDeletePackages(const std::vector& ids) { if (ids.empty()) { return Result(0); } int deleted = 0; for (const auto& id : ids) { auto result = deletePackage(id); if (result.isError()) { return result.error(); } deleted++; } return Result(deleted); } void Client::close() { // For in-memory implementation, optionally clear store // auto& store = getStore(); // store.users.clear(); // store.pages.clear(); // store.page_slugs.clear(); } }