2024-01-01 14:58:21 -05:00
|
|
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
2021-05-10 06:02:47 -04:00
|
|
|
|
2021-08-14 16:39:35 -04:00
|
|
|
// NOTE to all: use **cached** prepared statements when interfacing with SQLite.
|
|
|
|
|
2023-01-05 14:29:50 -05:00
|
|
|
use std::path::PathBuf;
|
|
|
|
|
2023-09-12 06:42:05 -04:00
|
|
|
use deno_core::op2;
|
2024-11-27 05:41:57 -05:00
|
|
|
use deno_core::GarbageCollected;
|
2021-05-10 06:02:47 -04:00
|
|
|
use deno_core::OpState;
|
|
|
|
use rusqlite::params;
|
|
|
|
use rusqlite::Connection;
|
|
|
|
use rusqlite::OptionalExtension;
|
|
|
|
|
2022-04-19 22:14:00 -04:00
|
|
|
pub use rusqlite;
|
|
|
|
|
2024-10-14 16:53:17 -04:00
|
|
|
#[derive(Debug, thiserror::Error)]
|
|
|
|
pub enum WebStorageError {
|
|
|
|
#[error("LocalStorage is not supported in this context.")]
|
|
|
|
ContextNotSupported,
|
|
|
|
#[error(transparent)]
|
|
|
|
Sqlite(#[from] rusqlite::Error),
|
|
|
|
#[error(transparent)]
|
|
|
|
Io(std::io::Error),
|
|
|
|
#[error("Exceeded maximum storage size")]
|
|
|
|
StorageExceeded,
|
|
|
|
}
|
|
|
|
|
2021-05-10 06:02:47 -04:00
|
|
|
#[derive(Clone)]
|
2021-05-27 01:23:12 -04:00
|
|
|
struct OriginStorageDir(PathBuf);
|
2021-05-10 06:02:47 -04:00
|
|
|
|
2023-03-09 07:28:51 -05:00
|
|
|
const MAX_STORAGE_BYTES: usize = 10 * 1024 * 1024;
|
2021-06-23 05:59:08 -04:00
|
|
|
|
2023-03-17 14:22:15 -04:00
|
|
|
deno_core::extension!(deno_webstorage,
|
|
|
|
deps = [ deno_webidl ],
|
|
|
|
ops = [
|
|
|
|
op_webstorage_iterate_keys,
|
|
|
|
],
|
2024-11-27 05:41:57 -05:00
|
|
|
objects = [
|
|
|
|
Storage
|
|
|
|
],
|
2023-03-17 14:22:15 -04:00
|
|
|
esm = [ "01_webstorage.js" ],
|
2023-03-17 18:15:27 -04:00
|
|
|
options = {
|
2024-11-27 05:41:57 -05:00
|
|
|
origin_storage_dir: Option<PathBuf>
|
2023-03-17 14:22:15 -04:00
|
|
|
},
|
2023-03-17 18:15:27 -04:00
|
|
|
state = |state, options| {
|
|
|
|
if let Some(origin_storage_dir) = options.origin_storage_dir {
|
2023-03-17 14:22:15 -04:00
|
|
|
state.put(OriginStorageDir(origin_storage_dir));
|
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
2023-03-09 09:56:19 -05:00
|
|
|
|
2022-03-16 20:25:44 -04:00
|
|
|
pub fn get_declaration() -> PathBuf {
|
|
|
|
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("lib.deno_webstorage.d.ts")
|
|
|
|
}
|
|
|
|
|
2021-05-19 08:28:50 -04:00
|
|
|
struct LocalStorage(Connection);
|
|
|
|
struct SessionStorage(Connection);
|
2021-05-10 06:02:47 -04:00
|
|
|
|
2021-05-19 08:28:50 -04:00
|
|
|
fn get_webstorage(
|
2021-05-10 06:02:47 -04:00
|
|
|
state: &mut OpState,
|
|
|
|
persistent: bool,
|
2024-10-14 16:53:17 -04:00
|
|
|
) -> Result<&Connection, WebStorageError> {
|
2021-05-19 08:28:50 -04:00
|
|
|
let conn = if persistent {
|
|
|
|
if state.try_borrow::<LocalStorage>().is_none() {
|
2024-10-14 16:53:17 -04:00
|
|
|
let path = state
|
|
|
|
.try_borrow::<OriginStorageDir>()
|
|
|
|
.ok_or(WebStorageError::ContextNotSupported)?;
|
|
|
|
std::fs::create_dir_all(&path.0).map_err(WebStorageError::Io)?;
|
2021-05-19 08:28:50 -04:00
|
|
|
let conn = Connection::open(path.0.join("local_storage"))?;
|
2021-08-14 16:39:35 -04:00
|
|
|
// Enable write-ahead-logging and tweak some other stuff.
|
|
|
|
let initial_pragmas = "
|
|
|
|
-- enable write-ahead-logging mode
|
|
|
|
PRAGMA journal_mode=WAL;
|
|
|
|
PRAGMA synchronous=NORMAL;
|
|
|
|
PRAGMA temp_store=memory;
|
|
|
|
PRAGMA page_size=4096;
|
|
|
|
PRAGMA mmap_size=6000000;
|
|
|
|
PRAGMA optimize;
|
|
|
|
";
|
|
|
|
|
|
|
|
conn.execute_batch(initial_pragmas)?;
|
|
|
|
conn.set_prepared_statement_cache_capacity(128);
|
|
|
|
{
|
|
|
|
let mut stmt = conn.prepare_cached(
|
|
|
|
"CREATE TABLE IF NOT EXISTS data (key VARCHAR UNIQUE, value VARCHAR)",
|
|
|
|
)?;
|
|
|
|
stmt.execute(params![])?;
|
|
|
|
}
|
2021-05-19 08:28:50 -04:00
|
|
|
state.put(LocalStorage(conn));
|
|
|
|
}
|
|
|
|
|
|
|
|
&state.borrow::<LocalStorage>().0
|
2021-05-10 06:02:47 -04:00
|
|
|
} else {
|
2021-05-19 08:28:50 -04:00
|
|
|
if state.try_borrow::<SessionStorage>().is_none() {
|
|
|
|
let conn = Connection::open_in_memory()?;
|
2021-08-14 16:39:35 -04:00
|
|
|
{
|
|
|
|
let mut stmt = conn.prepare_cached(
|
|
|
|
"CREATE TABLE data (key VARCHAR UNIQUE, value VARCHAR)",
|
|
|
|
)?;
|
|
|
|
stmt.execute(params![])?;
|
|
|
|
}
|
2021-05-19 08:28:50 -04:00
|
|
|
state.put(SessionStorage(conn));
|
|
|
|
}
|
|
|
|
|
|
|
|
&state.borrow::<SessionStorage>().0
|
|
|
|
};
|
2021-05-10 06:02:47 -04:00
|
|
|
|
2021-05-19 08:28:50 -04:00
|
|
|
Ok(conn)
|
2021-05-10 06:02:47 -04:00
|
|
|
}
|
|
|
|
|
2024-11-27 05:41:57 -05:00
|
|
|
#[inline]
|
|
|
|
fn size_check(input: usize) -> Result<(), WebStorageError> {
|
|
|
|
if input >= MAX_STORAGE_BYTES {
|
|
|
|
return Err(WebStorageError::StorageExceeded);
|
|
|
|
}
|
2021-05-10 06:02:47 -04:00
|
|
|
|
2024-11-27 05:41:57 -05:00
|
|
|
Ok(())
|
2021-05-10 06:02:47 -04:00
|
|
|
}
|
|
|
|
|
2024-11-27 05:41:57 -05:00
|
|
|
struct Storage {
|
2021-05-19 08:28:50 -04:00
|
|
|
persistent: bool,
|
2024-11-27 05:41:57 -05:00
|
|
|
}
|
2021-05-10 06:02:47 -04:00
|
|
|
|
2024-11-27 05:41:57 -05:00
|
|
|
impl GarbageCollected for Storage {}
|
2021-05-10 06:02:47 -04:00
|
|
|
|
2024-11-27 05:41:57 -05:00
|
|
|
#[op2]
|
|
|
|
impl Storage {
|
|
|
|
#[constructor]
|
|
|
|
#[cppgc]
|
|
|
|
fn new(persistent: bool) -> Storage {
|
|
|
|
Storage { persistent }
|
|
|
|
}
|
2021-05-10 06:02:47 -04:00
|
|
|
|
2024-11-27 05:41:57 -05:00
|
|
|
#[getter]
|
|
|
|
#[smi]
|
|
|
|
fn length(&self, state: &mut OpState) -> Result<u32, WebStorageError> {
|
|
|
|
let conn = get_webstorage(state, self.persistent)?;
|
2021-05-10 06:02:47 -04:00
|
|
|
|
2024-11-27 05:41:57 -05:00
|
|
|
let mut stmt = conn.prepare_cached("SELECT COUNT(*) FROM data")?;
|
|
|
|
let length: u32 = stmt.query_row(params![], |row| row.get(0))?;
|
|
|
|
|
|
|
|
Ok(length)
|
2023-03-09 07:28:51 -05:00
|
|
|
}
|
|
|
|
|
2024-11-27 05:41:57 -05:00
|
|
|
#[required(1)]
|
|
|
|
#[string]
|
|
|
|
fn key(
|
|
|
|
&self,
|
|
|
|
state: &mut OpState,
|
|
|
|
#[smi] index: u32,
|
|
|
|
) -> Result<Option<String>, WebStorageError> {
|
|
|
|
let conn = get_webstorage(state, self.persistent)?;
|
2023-03-09 07:28:51 -05:00
|
|
|
|
2024-11-27 05:41:57 -05:00
|
|
|
let mut stmt =
|
|
|
|
conn.prepare_cached("SELECT key FROM data LIMIT 1 OFFSET ?")?;
|
2021-05-19 08:28:50 -04:00
|
|
|
|
2024-11-27 05:41:57 -05:00
|
|
|
let key: Option<String> = stmt
|
|
|
|
.query_row(params![index], |row| row.get(0))
|
|
|
|
.optional()?;
|
2023-03-09 07:28:51 -05:00
|
|
|
|
2024-11-27 05:41:57 -05:00
|
|
|
Ok(key)
|
|
|
|
}
|
2021-05-10 06:02:47 -04:00
|
|
|
|
2024-11-27 05:41:57 -05:00
|
|
|
#[fast]
|
|
|
|
#[required(2)]
|
|
|
|
fn set_item(
|
|
|
|
&self,
|
|
|
|
state: &mut OpState,
|
|
|
|
#[string] key: &str,
|
|
|
|
#[string] value: &str,
|
|
|
|
) -> Result<(), WebStorageError> {
|
|
|
|
let conn = get_webstorage(state, self.persistent)?;
|
2021-05-10 06:02:47 -04:00
|
|
|
|
2024-11-27 05:41:57 -05:00
|
|
|
size_check(key.len() + value.len())?;
|
2021-05-10 06:02:47 -04:00
|
|
|
|
2024-11-27 05:41:57 -05:00
|
|
|
let mut stmt = conn
|
|
|
|
.prepare_cached("SELECT SUM(pgsize) FROM dbstat WHERE name = 'data'")?;
|
|
|
|
let size: u32 = stmt.query_row(params![], |row| row.get(0))?;
|
2021-05-10 06:02:47 -04:00
|
|
|
|
2024-11-27 05:41:57 -05:00
|
|
|
size_check(size as usize)?;
|
2021-05-10 06:02:47 -04:00
|
|
|
|
2024-11-27 05:41:57 -05:00
|
|
|
let mut stmt = conn.prepare_cached(
|
|
|
|
"INSERT OR REPLACE INTO data (key, value) VALUES (?, ?)",
|
|
|
|
)?;
|
|
|
|
stmt.execute(params![key, value])?;
|
2021-05-10 06:02:47 -04:00
|
|
|
|
2024-11-27 05:41:57 -05:00
|
|
|
Ok(())
|
|
|
|
}
|
2021-05-10 06:02:47 -04:00
|
|
|
|
2024-11-27 05:41:57 -05:00
|
|
|
#[required(1)]
|
|
|
|
#[string]
|
|
|
|
fn get_item(
|
|
|
|
&self,
|
|
|
|
state: &mut OpState,
|
|
|
|
#[string] key: &str,
|
|
|
|
) -> Result<Option<String>, WebStorageError> {
|
|
|
|
let conn = get_webstorage(state, self.persistent)?;
|
2021-05-10 06:02:47 -04:00
|
|
|
|
2024-11-27 05:41:57 -05:00
|
|
|
let mut stmt =
|
|
|
|
conn.prepare_cached("SELECT value FROM data WHERE key = ?")?;
|
|
|
|
let val = stmt.query_row(params![key], |row| row.get(0)).optional()?;
|
2021-05-10 06:02:47 -04:00
|
|
|
|
2024-11-27 05:41:57 -05:00
|
|
|
Ok(val)
|
|
|
|
}
|
2021-05-10 06:02:47 -04:00
|
|
|
|
2024-11-27 05:41:57 -05:00
|
|
|
#[fast]
|
|
|
|
#[required(1)]
|
|
|
|
fn remove_item(
|
|
|
|
&self,
|
|
|
|
state: &mut OpState,
|
|
|
|
#[string] key: &str,
|
|
|
|
) -> Result<(), WebStorageError> {
|
|
|
|
let conn = get_webstorage(state, self.persistent)?;
|
2021-05-10 06:02:47 -04:00
|
|
|
|
2024-11-27 05:41:57 -05:00
|
|
|
let mut stmt = conn.prepare_cached("DELETE FROM data WHERE key = ?")?;
|
|
|
|
stmt.execute(params![key])?;
|
2021-05-10 06:02:47 -04:00
|
|
|
|
2024-11-27 05:41:57 -05:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[fast]
|
|
|
|
fn clear(&self, state: &mut OpState) -> Result<(), WebStorageError> {
|
|
|
|
let conn = get_webstorage(state, self.persistent)?;
|
|
|
|
|
|
|
|
let mut stmt = conn.prepare_cached("DELETE FROM data")?;
|
|
|
|
stmt.execute(params![])?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2021-05-10 06:02:47 -04:00
|
|
|
}
|
|
|
|
|
2023-09-12 06:42:05 -04:00
|
|
|
#[op2]
|
|
|
|
#[serde]
|
2024-11-27 05:41:57 -05:00
|
|
|
fn op_webstorage_iterate_keys(
|
|
|
|
#[cppgc] storage: &Storage,
|
2021-05-10 06:02:47 -04:00
|
|
|
state: &mut OpState,
|
2024-10-14 16:53:17 -04:00
|
|
|
) -> Result<Vec<String>, WebStorageError> {
|
2024-11-27 05:41:57 -05:00
|
|
|
let conn = get_webstorage(state, storage.persistent)?;
|
2021-05-10 06:02:47 -04:00
|
|
|
|
2021-08-14 16:39:35 -04:00
|
|
|
let mut stmt = conn.prepare_cached("SELECT key FROM data")?;
|
2021-05-10 06:02:47 -04:00
|
|
|
let keys = stmt
|
|
|
|
.query_map(params![], |row| row.get::<_, String>(0))?
|
|
|
|
.map(|r| r.unwrap())
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
Ok(keys)
|
|
|
|
}
|