local kv = require("kv") local crypto = require("crypto") local json = require("json") local sessions = {} local stores = {} local default_store = nil -- ====================================================================== -- CORE FUNCTIONS -- ====================================================================== function sessions.init(store_name, filename) store_name = store_name or "sessions" if not kv.open(store_name, filename) then return false end stores[store_name] = true if not default_store then default_store = store_name end return true end function sessions.create(session_id, data, store_name) if type(session_id) ~= "string" then error("session ID must be a string", 2) end if data ~= nil and type(data) ~= "table" then error("data must be a table", 2) end store_name = store_name or default_store if not store_name then error("No session store initialized", 2) end local session_data = { data = data or {}, _created = os.time(), _last_accessed = os.time() } return kv.set(store_name, "session:" .. session_id, json.encode(session_data)) end function sessions.get(session_id, store_name) if type(session_id) ~= "string" then error("session ID must be a string", 2) end store_name = store_name or default_store if not store_name then error("No session store initialized", 2) end local json_str = kv.get(store_name, "session:" .. session_id) if not json_str then return nil end local session_data = json.decode(json_str) if not session_data then return nil end -- Update last accessed session_data._last_accessed = os.time() kv.set(store_name, "session:" .. session_id, json.encode(session_data)) -- Return flattened data with metadata local result = session_data.data or {} result._created = session_data._created result._last_accessed = session_data._last_accessed return result end function sessions.update(session_id, data, store_name) if type(session_id) ~= "string" then error("session ID must be a string", 2) end if type(data) ~= "table" then error("data must be a table", 2) end store_name = store_name or default_store if not store_name then error("No session store initialized", 2) end local json_str = kv.get(store_name, "session:" .. session_id) if not json_str then return false end local session_data = json.decode(json_str) if not session_data then return false end session_data.data = data session_data._last_accessed = os.time() return kv.set(store_name, "session:" .. session_id, json.encode(session_data)) end function sessions.delete(session_id, store_name) if type(session_id) ~= "string" then error("session ID must be a string", 2) end store_name = store_name or default_store if not store_name then error("No session store initialized", 2) end return kv.delete(store_name, "session:" .. session_id) end function sessions.cleanup(max_age, store_name) store_name = store_name or default_store if not store_name then error("No session store initialized", 2) end local keys = kv.keys(store_name) local current_time = os.time() local deleted = 0 for _, key in ipairs(keys) do if key:match("^session:") then local json_str = kv.get(store_name, key) if json_str then local session_data = json.decode(json_str) if session_data and session_data._last_accessed then if current_time - session_data._last_accessed > max_age then kv.delete(store_name, key) deleted = deleted + 1 end end end end end return deleted end function sessions.close(store_name) local success = kv.close(store_name) stores[store_name] = nil if default_store == store_name then default_store = next(stores) end return success end -- ====================================================================== -- UTILITIES -- ====================================================================== function sessions.generate_id() return crypto.random_alphanumeric(32) end function sessions.exists(session_id, store_name) store_name = store_name or default_store if not store_name then error("No session store initialized", 2) end return kv.has(store_name, "session:" .. session_id) end function sessions.list(store_name) store_name = store_name or default_store if not store_name then error("No session store initialized", 2) end local keys = kv.keys(store_name) local session_ids = {} for _, key in ipairs(keys) do local session_id = key:match("^session:(.+)") if session_id then table.insert(session_ids, session_id) end end return session_ids end function sessions.count(store_name) return #sessions.list(store_name) end function sessions.reset() stores = {} default_store = nil end -- ====================================================================== -- OOP INTERFACE -- ====================================================================== local SessionStore = {} SessionStore.__index = SessionStore function sessions.create_store(store_name, filename) if not sessions.init(store_name, filename) then error("Failed to initialize store '" .. store_name .. "'", 2) end return setmetatable({name = store_name}, SessionStore) end function SessionStore:create(session_id, data) return sessions.create(session_id, data, self.name) end function SessionStore:get(session_id) return sessions.get(session_id, self.name) end function SessionStore:update(session_id, data) return sessions.update(session_id, data, self.name) end function SessionStore:delete(session_id) return sessions.delete(session_id, self.name) end function SessionStore:cleanup(max_age) return sessions.cleanup(max_age, self.name) end function SessionStore:exists(session_id) return sessions.exists(session_id, self.name) end function SessionStore:list() return sessions.list(self.name) end function SessionStore:count() return sessions.count(self.name) end function SessionStore:close() return sessions.close(self.name) end return sessions