refactor: remove LuaScript entity and related operations

- Deleted all LuaScript CRUD operations from Client and entities.
- Removed LuaScript validation functions and related files.
- Updated InMemoryStore to remove LuaScript storage.
- Cleaned up tests by removing LuaScript related test cases.
- Adjusted documentation and quick start guide to reflect the removal of LuaScript functionality.
This commit is contained in:
2026-01-07 14:25:01 +00:00
parent 6d8b23e7a6
commit 7eee87ec90
27 changed files with 42 additions and 956 deletions

View File

@@ -37,12 +37,6 @@ public:
virtual Result<bool> deleteSession(const std::string& id) = 0;
virtual Result<std::vector<Session>> listSessions(const ListOptions& options) = 0;
virtual Result<LuaScript> createLuaScript(const CreateLuaScriptInput& input) = 0;
virtual Result<LuaScript> getLuaScript(const std::string& id) = 0;
virtual Result<LuaScript> updateLuaScript(const std::string& id, const UpdateLuaScriptInput& input) = 0;
virtual Result<bool> deleteLuaScript(const std::string& id) = 0;
virtual Result<std::vector<LuaScript>> listLuaScripts(const ListOptions& options) = 0;
virtual Result<InstalledPackage> createPackage(const CreatePackageInput& input) = 0;
virtual Result<InstalledPackage> getPackage(const std::string& id) = 0;
virtual Result<InstalledPackage> updatePackage(const std::string& id, const UpdatePackageInput& input) = 0;

View File

@@ -85,15 +85,6 @@ public:
Result<bool> deleteSession(const std::string& id);
Result<std::vector<Session>> listSessions(const ListOptions& options);
Result<LuaScript> createLuaScript(const CreateLuaScriptInput& input);
Result<LuaScript> getLuaScript(const std::string& id);
Result<LuaScript> updateLuaScript(const std::string& id, const UpdateLuaScriptInput& input);
Result<bool> deleteLuaScript(const std::string& id);
Result<std::vector<LuaScript>> listLuaScripts(const ListOptions& options);
Result<std::vector<LuaScript>> searchLuaScripts(const std::string& query,
const std::optional<std::string>& createdBy = std::nullopt,
int limit = 20);
Result<InstalledPackage> createPackage(const CreatePackageInput& input);
Result<InstalledPackage> getPackage(const std::string& id);
Result<InstalledPackage> updatePackage(const std::string& id, const UpdatePackageInput& input);

View File

@@ -158,38 +158,6 @@ struct UpdateSessionInput {
std::optional<std::string> userAgent;
};
struct CreateLuaScriptInput {
std::optional<std::string> tenantId;
std::string name;
std::optional<std::string> description;
std::string code;
std::string parameters;
std::optional<std::string> returnType;
bool isSandboxed = true;
std::string allowedGlobals;
int timeoutMs = 5000;
int version = 1;
std::optional<Timestamp> createdAt;
std::optional<Timestamp> updatedAt;
std::optional<std::string> createdBy;
};
struct UpdateLuaScriptInput {
std::optional<std::string> tenantId;
std::optional<std::string> name;
std::optional<std::string> description;
std::optional<std::string> code;
std::optional<std::string> parameters;
std::optional<std::string> returnType;
std::optional<bool> isSandboxed;
std::optional<std::string> allowedGlobals;
std::optional<int> timeoutMs;
std::optional<int> version;
std::optional<Timestamp> createdAt;
std::optional<Timestamp> updatedAt;
std::optional<std::string> createdBy;
};
struct CreatePackageInput {
std::string packageId;
std::optional<std::string> tenantId;

View File

@@ -199,32 +199,6 @@ Result<std::vector<Session>> Client::listSessions(const ListOptions& options) {
return entities::session::list(getStore(), options);
}
Result<LuaScript> Client::createLuaScript(const CreateLuaScriptInput& input) {
return entities::lua_script::create(getStore(), input);
}
Result<LuaScript> Client::getLuaScript(const std::string& id) {
return entities::lua_script::get(getStore(), id);
}
Result<LuaScript> Client::updateLuaScript(const std::string& id, const UpdateLuaScriptInput& input) {
return entities::lua_script::update(getStore(), id, input);
}
Result<bool> Client::deleteLuaScript(const std::string& id) {
return entities::lua_script::remove(getStore(), id);
}
Result<std::vector<LuaScript>> Client::listLuaScripts(const ListOptions& options) {
return entities::lua_script::list(getStore(), options);
}
Result<std::vector<LuaScript>> Client::searchLuaScripts(const std::string& query,
const std::optional<std::string>& createdBy,
int limit) {
return entities::lua_script::search(getStore(), query, createdBy, limit);
}
Result<InstalledPackage> Client::createPackage(const CreatePackageInput& input) {
return entities::package::create(getStore(), input);
}

View File

@@ -10,8 +10,7 @@
#include "user_operations.hpp"
#include "page_operations.hpp"
#include "workflow_operations.hpp"
#include "session_operations.hpp"
#include "lua_script_operations.hpp"
#include "package_operations.hpp"
#include "session_operations.hpp"
#include "package_operations.hpp"
#endif

View File

@@ -8,13 +8,12 @@
#ifndef DBAL_ENTITIES_INDEX_HPP
#define DBAL_ENTITIES_INDEX_HPP
#include "user/index.hpp"
#include "page/index.hpp"
#include "component/index.hpp"
#include "workflow/index.hpp"
#include "session/index.hpp"
#include "lua_script/index.hpp"
#include "package/index.hpp"
#include "user/index.hpp"
#include "page/index.hpp"
#include "component/index.hpp"
#include "workflow/index.hpp"
#include "session/index.hpp"
#include "package/index.hpp"
#include "credential/index.hpp"
#endif

View File

@@ -1,65 +0,0 @@
/**
* @file create_lua_script.hpp
* @brief Create Lua script operation
*/
#ifndef DBAL_CREATE_LUA_SCRIPT_HPP
#define DBAL_CREATE_LUA_SCRIPT_HPP
#include "dbal/types.hpp"
#include "dbal/errors.hpp"
#include "../../../store/in_memory_store.hpp"
#include "../../../validation/entity/lua_script_validation.hpp"
namespace dbal {
namespace entities {
namespace lua_script {
/**
* Create a new Lua script in the store
*/
inline Result<LuaScript> create(InMemoryStore& store, const CreateLuaScriptInput& input) {
if (!validation::isValidLuaScriptName(input.name)) {
return Error::validationError("Lua script name must be 1-255 characters");
}
if (!validation::isValidLuaScriptCode(input.code)) {
return Error::validationError("Lua script code must be a non-empty string");
}
if (!validation::isValidLuaTimeout(input.timeoutMs)) {
return Error::validationError("Timeout must be between 100 and 30000 ms");
}
std::string globals_error;
if (!validation::validateLuaAllowedGlobals(input.allowedGlobals, globals_error)) {
return Error::validationError(globals_error);
}
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 = store.generateId("lua", ++store.lua_script_counter);
script.tenantId = input.tenantId;
script.name = input.name;
script.description = input.description;
script.code = input.code;
script.parameters = input.parameters;
script.returnType = input.returnType;
script.isSandboxed = input.isSandboxed;
script.allowedGlobals = input.allowedGlobals;
script.timeoutMs = input.timeoutMs;
script.version = input.version;
script.createdAt = input.createdAt.value_or(std::chrono::system_clock::now());
script.updatedAt = input.updatedAt.value_or(script.createdAt);
script.createdBy = input.createdBy;
store.lua_scripts[script.id] = script;
store.lua_script_names[script.name] = script.id;
return Result<LuaScript>(script);
}
} // namespace lua_script
} // namespace entities
} // namespace dbal
#endif

View File

@@ -1,39 +0,0 @@
/**
* @file delete_lua_script.hpp
* @brief Delete Lua script operation
*/
#ifndef DBAL_DELETE_LUA_SCRIPT_HPP
#define DBAL_DELETE_LUA_SCRIPT_HPP
#include "dbal/types.hpp"
#include "dbal/errors.hpp"
#include "../../../store/in_memory_store.hpp"
namespace dbal {
namespace entities {
namespace lua_script {
/**
* Delete a Lua script by ID
*/
inline Result<bool> remove(InMemoryStore& store, const std::string& id) {
if (id.empty()) {
return Error::validationError("Lua script ID cannot be empty");
}
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<bool>(true);
}
} // namespace lua_script
} // namespace entities
} // namespace dbal
#endif

View File

@@ -1,36 +0,0 @@
/**
* @file get_lua_script.hpp
* @brief Get Lua script by ID operation
*/
#ifndef DBAL_GET_LUA_SCRIPT_HPP
#define DBAL_GET_LUA_SCRIPT_HPP
#include "dbal/types.hpp"
#include "dbal/errors.hpp"
#include "../../../store/in_memory_store.hpp"
namespace dbal {
namespace entities {
namespace lua_script {
/**
* Get a Lua script by ID
*/
inline Result<LuaScript> get(InMemoryStore& store, const std::string& id) {
if (id.empty()) {
return Error::validationError("Lua script ID cannot be empty");
}
auto it = store.lua_scripts.find(id);
if (it == store.lua_scripts.end()) {
return Error::notFound("Lua script not found: " + id);
}
return Result<LuaScript>(it->second);
}
} // namespace lua_script
} // namespace entities
} // namespace dbal
#endif

View File

@@ -1,67 +0,0 @@
/**
* @file list_lua_scripts.hpp
* @brief List Lua scripts with filtering and pagination
*/
#ifndef DBAL_LIST_LUA_SCRIPTS_HPP
#define DBAL_LIST_LUA_SCRIPTS_HPP
#include "dbal/types.hpp"
#include "dbal/errors.hpp"
#include "../../../store/in_memory_store.hpp"
#include <algorithm>
namespace dbal {
namespace entities {
namespace lua_script {
/**
* List Lua scripts with filtering and pagination
*/
inline Result<std::vector<LuaScript>> list(InMemoryStore& store, const ListOptions& options) {
std::vector<LuaScript> scripts;
for (const auto& [id, script] : store.lua_scripts) {
bool matches = true;
if (options.filter.find("createdBy") != options.filter.end()) {
if (!script.createdBy.has_value() || script.createdBy.value() != options.filter.at("createdBy")) {
matches = false;
}
}
if (options.filter.find("isSandboxed") != options.filter.end()) {
bool filter_sandboxed = options.filter.at("isSandboxed") == "true";
if (script.isSandboxed != 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("createdAt") != options.sort.end()) {
std::sort(scripts.begin(), scripts.end(), [](const LuaScript& a, const LuaScript& b) {
return a.createdAt.value_or(std::chrono::system_clock::time_point()) <
b.createdAt.value_or(std::chrono::system_clock::time_point());
});
}
int start = (options.page - 1) * options.limit;
int end = std::min(start + options.limit, static_cast<int>(scripts.size()));
if (start < static_cast<int>(scripts.size())) {
return Result<std::vector<LuaScript>>(std::vector<LuaScript>(scripts.begin() + start, scripts.begin() + end));
}
return Result<std::vector<LuaScript>>(std::vector<LuaScript>());
}
} // namespace lua_script
} // namespace entities
} // namespace dbal
#endif

View File

@@ -1,71 +0,0 @@
#ifndef DBAL_SEARCH_LUA_SCRIPTS_HPP
#define DBAL_SEARCH_LUA_SCRIPTS_HPP
#include "dbal/errors.hpp"
#include "../../../store/in_memory_store.hpp"
#include <algorithm>
#include <cctype>
#include <iterator>
#include <optional>
#include <string>
#include <vector>
namespace dbal {
namespace entities {
namespace lua_script {
namespace {
inline std::string toLower(const std::string& value) {
std::string lowered;
lowered.reserve(value.size());
std::transform(value.begin(), value.end(), std::back_inserter(lowered), [](unsigned char c) {
return static_cast<char>(std::tolower(c));
});
return lowered;
}
inline bool containsInsensitive(const std::string& text, const std::string& query) {
if (query.empty()) {
return false;
}
return toLower(text).find(toLower(query)) != std::string::npos;
}
} // namespace
inline Result<std::vector<LuaScript>> search(InMemoryStore& store,
const std::string& query,
const std::optional<std::string>& createdBy = std::nullopt,
int limit = 20) {
if (query.empty()) {
return Error::validationError("search query is required");
}
std::vector<LuaScript> matches;
for (const auto& [id, script] : store.lua_scripts) {
(void)id;
if (createdBy.has_value() && (!script.createdBy.has_value() || script.createdBy.value() != createdBy.value())) {
continue;
}
if (containsInsensitive(script.name, query) || containsInsensitive(script.code, query)) {
matches.push_back(script);
}
}
std::sort(matches.begin(), matches.end(), [](const LuaScript& a, const LuaScript& b) {
return a.name < b.name;
});
if (limit > 0 && static_cast<int>(matches.size()) > limit) {
matches.resize(limit);
}
return Result<std::vector<LuaScript>>(matches);
}
} // namespace lua_script
} // namespace entities
} // namespace dbal
#endif

View File

@@ -1,113 +0,0 @@
/**
* @file update_lua_script.hpp
* @brief Update Lua script operation
*/
#ifndef DBAL_UPDATE_LUA_SCRIPT_HPP
#define DBAL_UPDATE_LUA_SCRIPT_HPP
#include "dbal/types.hpp"
#include "dbal/errors.hpp"
#include "../../../store/in_memory_store.hpp"
#include "../../../validation/entity/lua_script_validation.hpp"
namespace dbal {
namespace entities {
namespace lua_script {
/**
* Update an existing Lua script
*/
inline Result<LuaScript> update(InMemoryStore& store, const std::string& id, const UpdateLuaScriptInput& input) {
if (id.empty()) {
return Error::validationError("Lua script ID cannot be empty");
}
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 (!validation::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 (!validation::isValidLuaScriptCode(input.code.value())) {
return Error::validationError("Lua script code must be a non-empty string");
}
script.code = input.code.value();
}
if (input.parameters.has_value()) {
script.parameters = input.parameters.value();
}
if (input.returnType.has_value()) {
script.returnType = input.returnType.value();
}
if (input.isSandboxed.has_value()) {
script.isSandboxed = input.isSandboxed.value();
}
if (input.allowedGlobals.has_value()) {
std::string globals_error;
if (!validation::validateLuaAllowedGlobals(input.allowedGlobals.value(), globals_error)) {
return Error::validationError(globals_error);
}
script.allowedGlobals = input.allowedGlobals.value();
}
if (input.timeoutMs.has_value()) {
if (!validation::isValidLuaTimeout(input.timeoutMs.value())) {
return Error::validationError("Timeout must be between 100 and 30000 ms");
}
script.timeoutMs = input.timeoutMs.value();
}
if (input.version.has_value()) {
script.version = input.version.value();
}
if (input.createdAt.has_value()) {
script.createdAt = input.createdAt.value();
}
if (input.updatedAt.has_value()) {
script.updatedAt = input.updatedAt.value();
} else {
script.updatedAt = std::chrono::system_clock::now();
}
if (input.createdBy.has_value()) {
script.createdBy = input.createdBy.value();
}
if (input.tenantId.has_value()) {
script.tenantId = input.tenantId.value();
}
return Result<LuaScript>(script);
}
} // namespace lua_script
} // namespace entities
} // namespace dbal
#endif

View File

@@ -1,15 +0,0 @@
/**
* @file index.hpp
* @brief Barrel include for Lua script operations
*/
#ifndef DBAL_LUA_SCRIPT_INDEX_HPP
#define DBAL_LUA_SCRIPT_INDEX_HPP
#include "crud/create_lua_script.hpp"
#include "crud/get_lua_script.hpp"
#include "crud/update_lua_script.hpp"
#include "crud/delete_lua_script.hpp"
#include "crud/list_lua_scripts.hpp"
#include "crud/search_lua_scripts.hpp"
#endif

View File

@@ -24,7 +24,6 @@ struct InMemoryStore {
std::map<std::string, PageConfig> pages;
std::map<std::string, Workflow> workflows;
std::map<std::string, Session> sessions;
std::map<std::string, LuaScript> lua_scripts;
std::map<std::string, InstalledPackage> packages;
std::map<std::string, Credential> credentials;
@@ -32,7 +31,6 @@ struct InMemoryStore {
std::map<std::string, std::string> page_paths; // path -> id
std::map<std::string, std::string> workflow_names; // name -> id
std::map<std::string, std::string> session_tokens; // token -> id
std::map<std::string, std::string> lua_script_names; // name -> id
std::map<std::string, std::string> package_keys; // packageId -> id
// Entity counters for ID generation
@@ -40,7 +38,6 @@ struct InMemoryStore {
int page_counter = 0;
int workflow_counter = 0;
int session_counter = 0;
int lua_script_counter = 0;
int package_counter = 0;
int credential_counter = 0;
@@ -69,8 +66,6 @@ struct InMemoryStore {
workflow_names.clear();
sessions.clear();
session_tokens.clear();
lua_scripts.clear();
lua_script_names.clear();
packages.clear();
package_keys.clear();
credentials.clear();
@@ -82,7 +77,6 @@ struct InMemoryStore {
page_counter = 0;
workflow_counter = 0;
session_counter = 0;
lua_script_counter = 0;
package_counter = 0;
credential_counter = 0;
component_counter = 0;

View File

@@ -1,61 +0,0 @@
/**
* @file lua_script_validation.hpp
* @brief Validation functions for LuaScript entity
*/
#ifndef DBAL_LUA_SCRIPT_VALIDATION_HPP
#define DBAL_LUA_SCRIPT_VALIDATION_HPP
#include <string>
#include <unordered_set>
namespace dbal {
namespace validation {
/**
* Validate Lua script name (1-255 characters)
*/
inline bool isValidLuaScriptName(const std::string& name) {
return !name.empty() && name.length() <= 255;
}
/**
* Validate Lua script code (non-empty)
*/
inline bool isValidLuaScriptCode(const std::string& code) {
return !code.empty();
}
/**
* Validate Lua script timeout (100-30000 ms)
*/
inline bool isValidLuaTimeout(int timeoutMs) {
return timeoutMs >= 100 && timeoutMs <= 30000;
}
/**
* Check if a global name is allowed in the Lua sandbox
*/
inline bool isAllowedLuaGlobal(const std::string& name) {
static const std::unordered_set<std::string> allowed = {
"assert", "error", "ipairs", "next", "pairs", "pcall", "select",
"tonumber", "tostring", "type", "unpack", "xpcall",
"string", "table", "math", "bit32", "print", "log"
};
return allowed.find(name) != allowed.end();
}
/**
* Validate allowed globals list for Lua scripts
*/
inline bool validateLuaAllowedGlobals(const std::string& globals, std::string& error) {
if (globals.empty()) {
error = "allowedGlobals must be a JSON string";
return false;
}
return true;
}
} // namespace validation
} // namespace dbal
#endif

View File

@@ -1,4 +0,0 @@
#pragma once
#include "entity/lua_script_validation.hpp"

View File

@@ -10,9 +10,8 @@
#include "user_validation.hpp"
#include "page_validation.hpp"
#include "entity/component_validation.hpp"
#include "workflow_validation.hpp"
#include "lua_script_validation.hpp"
#include "package_validation.hpp"
#include "workflow_validation.hpp"
#include "package_validation.hpp"
#include "entity/credential_validation.hpp"
#endif

View File

@@ -1155,148 +1155,6 @@ void test_session_validation() {
std::cout << " ✓ Duplicate token rejected" << std::endl;
}
void test_lua_script_crud() {
std::cout << "Testing Lua script CRUD operations..." << std::endl;
dbal::ClientConfig config;
config.adapter = "sqlite";
config.database_url = ":memory:";
dbal::Client client(config);
dbal::CreateUserInput userInput;
userInput.username = "lua_owner";
userInput.email = "lua_owner@example.com";
auto userResult = client.createUser(userInput);
assert(userResult.isOk());
dbal::CreateLuaScriptInput input;
input.name = "health_check";
input.description = "Health check";
input.code = "return true";
input.parameters = "[]";
input.isSandboxed = true;
input.allowedGlobals = "[]";
input.timeoutMs = 1000;
input.createdBy = userResult.value().id;
auto createResult = client.createLuaScript(input);
assert(createResult.isOk());
std::string scriptId = createResult.value().id;
std::cout << " ✓ Lua script created with ID: " << scriptId << std::endl;
auto getResult = client.getLuaScript(scriptId);
assert(getResult.isOk());
assert(getResult.value().name == "health_check");
std::cout << " ✓ Retrieved Lua script by ID" << std::endl;
dbal::UpdateLuaScriptInput updateInput;
updateInput.timeoutMs = 2000;
updateInput.isSandboxed = false;
auto updateResult = client.updateLuaScript(scriptId, updateInput);
assert(updateResult.isOk());
assert(updateResult.value().timeoutMs == 2000);
std::cout << " ✓ Lua script updated" << std::endl;
dbal::ListOptions listOptions;
listOptions.filter["isSandboxed"] = "false";
auto listResult = client.listLuaScripts(listOptions);
assert(listResult.isOk());
assert(listResult.value().size() >= 1);
std::cout << " ✓ Listed Lua scripts (filtered by isSandboxed=false)" << std::endl;
auto deleteResult = client.deleteLuaScript(scriptId);
assert(deleteResult.isOk());
auto notFoundResult = client.getLuaScript(scriptId);
assert(notFoundResult.isError());
std::cout << " ✓ Lua script deleted" << std::endl;
}
void test_lua_script_validation() {
std::cout << "Testing Lua script validation..." << std::endl;
dbal::ClientConfig config;
config.adapter = "sqlite";
config.database_url = ":memory:";
dbal::Client client(config);
dbal::CreateUserInput userInput;
userInput.username = "lua_validator";
userInput.email = "lua_validator@example.com";
auto userResult = client.createUser(userInput);
assert(userResult.isOk());
dbal::CreateLuaScriptInput input1;
input1.name = "invalid-timeout";
input1.code = "return true";
input1.parameters = "[]";
input1.isSandboxed = true;
input1.allowedGlobals = "[]";
input1.timeoutMs = 50;
input1.createdBy = userResult.value().id;
auto result1 = client.createLuaScript(input1);
assert(result1.isError());
assert(result1.error().code() == dbal::ErrorCode::ValidationError);
std::cout << " ✓ Invalid timeout rejected" << std::endl;
dbal::CreateLuaScriptInput input2;
input2.name = "duplicate-script";
input2.code = "return true";
input2.parameters = "[]";
input2.isSandboxed = true;
input2.allowedGlobals = "[]";
input2.timeoutMs = 1000;
input2.createdBy = userResult.value().id;
auto result2 = client.createLuaScript(input2);
assert(result2.isOk());
dbal::CreateLuaScriptInput input3 = input2;
auto result3 = client.createLuaScript(input3);
assert(result3.isError());
assert(result3.error().code() == dbal::ErrorCode::Conflict);
std::cout << " ✓ Duplicate script name rejected" << std::endl;
}
void test_lua_script_search() {
std::cout << "Testing Lua script search..." << std::endl;
dbal::ClientConfig config;
config.adapter = "sqlite";
config.database_url = ":memory:";
dbal::Client client(config);
dbal::CreateUserInput userInput;
userInput.username = "lua_search_owner";
userInput.email = "lua_search_owner@example.com";
auto userResult = client.createUser(userInput);
assert(userResult.isOk());
dbal::CreateLuaScriptInput scriptInput;
scriptInput.name = "search_script";
scriptInput.code = "return 'search'";
scriptInput.parameters = "[]";
scriptInput.allowedGlobals = "[]";
scriptInput.createdBy = userResult.value().id;
auto createResult = client.createLuaScript(scriptInput);
assert(createResult.isOk());
dbal::CreateLuaScriptInput otherInput = scriptInput;
otherInput.name = "other_script";
otherInput.code = "return 'other'";
auto otherResult = client.createLuaScript(otherInput);
assert(otherResult.isOk());
auto searchResult = client.searchLuaScripts("search", userResult.value().id, 10);
assert(searchResult.isOk());
assert(searchResult.value().size() == 1);
std::cout << " ✓ Script name search works" << std::endl;
auto codeSearch = client.searchLuaScripts("return 'other'", std::nullopt, 10);
assert(codeSearch.isOk());
assert(codeSearch.value().size() >= 1);
std::cout << " ✓ Script code search works" << std::endl;
}
void test_package_crud() {
std::cout << "Testing package CRUD operations..." << std::endl;
@@ -1316,7 +1174,7 @@ void test_package_crud() {
input.version = "1.2.3";
input.installedAt = std::chrono::system_clock::now();
input.enabled = false;
input.config = "{\"entry\":\"index.lua\"}";
input.config = "{\"entry\":\"index.js\"}";
auto createResult = client.createPackage(input);
assert(createResult.isOk());
@@ -1498,9 +1356,6 @@ int main() {
test_workflow_validation();
test_session_crud();
test_session_validation();
test_lua_script_crud();
test_lua_script_validation();
test_lua_script_search();
test_package_crud();
test_package_validation();
test_package_batch_operations();
@@ -1508,7 +1363,7 @@ int main() {
std::cout << std::endl;
std::cout << "==================================================" << std::endl;
std::cout << "✅ All 33 test suites passed!" << std::endl;
std::cout << "✅ All test suites passed!" << std::endl;
std::cout << "==================================================" << std::endl;
return 0;
} catch (const std::exception& e) {

View File

@@ -195,63 +195,6 @@
status: success
output: true
- name: "LuaScript CRUD operations"
description: "Test basic create, read, update, delete operations for LuaScript entity"
operations:
- action: create
entity: User
input:
username: "lua_owner"
email: "lua_owner@example.com"
role: "admin"
expected:
status: success
- action: create
entity: LuaScript
input:
name: "health_check"
description: "Simple health check"
code: "return true"
isSandboxed: true
allowedGlobals: ["math"]
timeoutMs: 1000
createdBy: "$steps[0].id"
expected:
status: success
output:
name: "health_check"
isSandboxed: true
- action: read
entity: LuaScript
input:
id: "$steps[1].id"
expected:
status: success
output:
name: "health_check"
- action: update
entity: LuaScript
input:
id: "$steps[1].id"
isSandboxed: false
timeoutMs: 2000
expected:
status: success
output:
isSandboxed: false
timeoutMs: 2000
- action: delete
entity: LuaScript
input:
id: "$steps[1].id"
expected:
status: success
output: true
- name: "Package CRUD operations"
description: "Test basic create, read, update, delete operations for Package entity"
operations:
@@ -271,8 +214,8 @@
version: "1.2.3"
description: "Forum package"
author: "MetaBuilder"
manifest:
entry: "index.lua"
manifest:
entry: "index.js"
isInstalled: false
expected:
status: success

View File

@@ -125,14 +125,13 @@ Check browser console for `[DBAL Audit]` logs.
Full TypeScript support:
```typescript
import type { User, PageConfig, ComponentNode, Workflow, LuaScript, InstalledPackage, Session } from '../../dbal/development/src'
import type { User, PageConfig, ComponentNode, Workflow, InstalledPackage, Session } from '../../dbal/development/src'
// Type-safe entities
const user: User = await client.users.create({ ... })
const page: PageConfig = await client.pageConfigs.create({ ... })
const component: ComponentNode = await client.componentNodes.create({ ... })
const workflow: Workflow = await client.workflows.create({ ... })
const script: LuaScript = await client.luaScripts.create({ ... })
const pkg: InstalledPackage = await client.installedPackages.create({ ... })
const session: Session = await client.sessions.create({ ... })
@@ -182,28 +181,6 @@ client.workflows.delete(id)
client.workflows.list(options)
```
### Lua Scripts
```typescript
client.luaScripts.create(data)
client.luaScripts.read(id)
client.luaScripts.update(id, data)
client.luaScripts.delete(id)
client.luaScripts.list(options)
```
```typescript
const script = await client.luaScripts.create({
name: 'health_check',
description: 'Simple health check',
code: 'return true',
parameters: JSON.stringify([]),
isSandboxed: true,
allowedGlobals: JSON.stringify(['math']),
timeoutMs: 1000,
createdBy: '11111111-1111-1111-1111-111111111111',
})
```
### Packages
```typescript
client.installedPackages.create(data)

View File

@@ -28,8 +28,8 @@ export const metadata: Metadata = {
template: '%s | MetaBuilder',
},
description:
'A data-driven, multi-tenant application platform where 95% of functionality is defined through JSON and Lua.',
keywords: ['metabuilder', 'low-code', 'no-code', 'lua', 'platform', 'multi-tenant'],
'A data-driven, multi-tenant platform where every experience is powered by JSON packages.',
keywords: ['metabuilder', 'low-code', 'no-code', 'platform', 'multi-tenant'],
authors: [{ name: 'MetaBuilder Team' }],
creator: 'MetaBuilder',
icons: {

View File

@@ -1,9 +1,9 @@
import type { Metadata } from 'next'
import { notFound } from 'next/navigation'
import type { JSONComponent } from '@/lib/packages/json/types'
import { UIPageRenderer } from '@/components/ui-page-renderer/UIPageRenderer'
import { loadPageFromDb } from '@/lib/ui-pages/load-page-from-db'
import { loadPageFromLuaPackages } from '@/lib/ui-pages/load-page-from-lua-packages'
interface PageProps {
params: Promise<{
@@ -17,33 +17,32 @@ interface PageProps {
*
* Flow:
* 1. JSON seed data → Database (via import-ui-pages.ts)
* 2. Database → Load page record
* 3. Lua actions → Execute if present
* 4. UIPageRenderer → Generate React components
* 5. User sees rendered page
* 2. Database → Load component tree
* 3. UIPageRenderer → Generate React components
* 4. User sees rendered page
*/
export default async function DynamicUIPage({ params }: PageProps) {
const resolvedParams = await params
const slug = resolvedParams.slug ?? []
const path = '/' + slug.join('/')
// Prefer Lua package-based UI pages, fallback to database-backed pages
const rawPageData = (await loadPageFromLuaPackages(path)) ?? (await loadPageFromDb(path))
const rawPageData = await loadPageFromDb(path)
if (rawPageData === null) {
notFound()
}
// Transform PageConfig to UIPageData
const pageData = {
layout: rawPageData,
actions: {},
const componentTree = rawPageData.componentTree
if (componentTree === null || componentTree === undefined) {
notFound()
}
// Check authentication if required
// TODO: Add auth check based on pageData.requireAuth and pageData.requiredRole
const layout = componentTree as JSONComponent
if (typeof layout !== 'object' || Array.isArray(layout)) {
notFound()
}
return <UIPageRenderer pageData={pageData} />
return <UIPageRenderer layout={layout} actions={{}} />
}
/**
@@ -54,7 +53,7 @@ export async function generateMetadata({ params }: PageProps): Promise<Metadata>
const slug = resolvedParams.slug ?? []
const path = '/' + slug.join('/')
const pageData = (await loadPageFromLuaPackages(path)) ?? (await loadPageFromDb(path))
const pageData = await loadPageFromDb(path)
if (pageData === null) {
return {

View File

@@ -2,28 +2,25 @@
import React from 'react'
import { generateComponentTree } from '@/lib/lua/ui/generate-component-tree'
import type { LuaUIComponent } from '@/lib/lua/ui/types/lua-ui-package'
import type { LuaActionHandler, UIPageData } from '@/lib/ui-pages/load-page-from-db'
import { renderJSONComponent } from '@/lib/packages/json/render-json-component'
import type { JSONComponent } from '@/lib/packages/json/types'
type PageActionHandler = (action: string, data: Record<string, unknown>) => void | Promise<void>
interface UIPageRendererProps {
pageData: UIPageData
layout: JSONComponent
actions?: Record<string, PageActionHandler>
}
/**
* Generic TSX renderer for database-loaded UI pages
* Flow: Database → Lua → This Component → React Elements → User
* Flow: Database → JSON component → React Elements → User
*/
export function UIPageRenderer({ pageData }: UIPageRendererProps) {
// Convert JSON layout to LuaUIComponent structure
const layout = pageData.layout as LuaUIComponent
export function UIPageRenderer({ layout, actions = {} }: UIPageRendererProps) {
const elements = React.useMemo(() => renderJSONComponent(layout), [layout])
// Create React elements from component tree
const elements = generateComponentTree(layout)
// Provide action handlers via context
return (
<UIPageActionsContext.Provider value={pageData.actions ?? {}}>
<UIPageActionsContext.Provider value={actions}>
{elements}
</UIPageActionsContext.Provider>
)
@@ -33,7 +30,7 @@ export function UIPageRenderer({ pageData }: UIPageRendererProps) {
* Context for action handlers
* Components can access these via useUIPageActions hook
*/
const UIPageActionsContext = React.createContext<Record<string, LuaActionHandler>>({})
const UIPageActionsContext = React.createContext<Record<string, PageActionHandler>>({})
/**
* Hook to access page action handlers

View File

@@ -1,100 +0,0 @@
/**
* Generate React component tree from Lua UI component definitions
*
* Transforms LuaUIComponent structures into React elements using
* the JSON component renderer infrastructure.
*/
import React, { type ReactNode } from 'react'
import type { LuaUIComponent } from './types/lua-ui-package'
export interface ComponentTree {
type: string
props?: Record<string, unknown>
children?: ComponentTree[]
}
/**
* Map of basic HTML elements that can be rendered directly
*/
const HTML_ELEMENTS = new Set([
'div', 'span', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
'section', 'article', 'header', 'footer', 'main', 'aside', 'nav',
'ul', 'ol', 'li', 'dl', 'dt', 'dd',
'table', 'thead', 'tbody', 'tfoot', 'tr', 'th', 'td',
'form', 'input', 'button', 'select', 'option', 'textarea', 'label',
'a', 'img', 'video', 'audio', 'canvas', 'svg',
'strong', 'em', 'code', 'pre', 'blockquote',
])
/**
* Render a single LuaUIComponent to a React element
*/
function renderComponent(component: LuaUIComponent, key?: string | number): ReactNode {
const { type, props = {}, children } = component
// Render children recursively
const renderedChildren = children?.map((child, index) =>
renderComponent(child, index)
)
// Handle text content
if (type === 'text' && typeof props.content === 'string') {
return props.content
}
// Handle HTML elements
if (HTML_ELEMENTS.has(type)) {
return React.createElement(
type,
{ ...props, key },
renderedChildren
)
}
// Handle custom components - wrap in div with data attribute for future component registry
return React.createElement(
'div',
{
'data-component': type,
className: `component-${type}`,
...props,
key,
},
renderedChildren ?? (
<span className="component-placeholder">
[{type}]
</span>
)
)
}
/**
* Generate a complete React component tree from a Lua UI component definition.
*
* @param luaComponent - The root LuaUIComponent to render
* @returns React node tree, or null if input is invalid
*/
export function generateComponentTree(luaComponent: unknown): ReactNode {
// Validate input
if (luaComponent === null || luaComponent === undefined) {
return null
}
if (typeof luaComponent !== 'object') {
return null
}
const component = luaComponent as LuaUIComponent
// Must have a type to render
if (typeof component.type !== 'string' || component.type.length === 0) {
return (
<div className="component-error">
Invalid component: missing type
</div>
)
}
return renderComponent(component)
}

View File

@@ -1,15 +0,0 @@
/**
* Lua UI types (stub)
*/
export interface LuaUIComponent {
type: string
props?: Record<string, unknown>
children?: LuaUIComponent[]
}
export interface LuaUIPackage {
id: string
name: string
components: unknown[]
}

View File

@@ -5,13 +5,6 @@
import type { PageConfig } from '../types/level-types'
import { prisma } from '@/lib/config/prisma'
export type LuaActionHandler = (action: string, data: Record<string, unknown>) => void | Promise<void>
export interface UIPageData {
layout: unknown
actions?: Record<string, LuaActionHandler>
}
export async function loadPageFromDb(path: string, tenantId?: string): Promise<PageConfig | null> {
// Prisma client typing is generated; suppress lint in environments without generated types.

View File

@@ -1,10 +0,0 @@
/**
* Load UI page from Lua packages (stub)
*/
import type { PageConfig } from '../types/level-types'
export function loadPageFromLuaPackages(_b_path: string): Promise<PageConfig | null> {
// TODO: Implement page loading from Lua packages
return Promise.resolve(null)
}