use std::{borrow::Cow, path::PathBuf};
use deno_core::{extension, Extension};
use deno_kv::{
dynamic::MultiBackendDbHandler,
remote::{RemoteDbHandler, RemoteDbHandlerPermissions},
sqlite::{SqliteDbHandler, SqliteDbHandlerPermissions},
};
use deno_permissions::{CheckedPath, PermissionCheckError, PermissionDeniedError};
use super::{web::PermissionsContainer, ExtensionTrait};
extension!(
init_kv,
deps = [rustyscript],
esm_entry_point = "ext:init_kv/init_kv.js",
esm = [ dir "src/ext/kv", "init_kv.js" ],
);
impl ExtensionTrait<()> for init_kv {
fn init((): ()) -> Extension {
init_kv::init()
}
}
impl ExtensionTrait<KvStore> for deno_kv::deno_kv {
fn init(store: KvStore) -> Extension {
deno_kv::deno_kv::init(store.handler(), store.config())
}
}
pub fn extensions(store: KvStore, is_snapshot: bool) -> Vec<Extension> {
vec![
deno_kv::deno_kv::build(store, is_snapshot),
init_kv::build((), is_snapshot),
]
}
#[derive(Clone)]
enum KvStoreBuilder {
Local {
path: Option<PathBuf>,
rng_seed: Option<u64>,
},
Remote {
http_options: deno_kv::remote::HttpOptions,
},
}
#[derive(Clone, Copy)]
#[allow(clippy::struct_field_names)]
pub struct KvConfig {
pub max_write_key_size_bytes: usize,
pub max_value_size_bytes: usize,
pub max_read_ranges: usize,
pub max_read_entries: usize,
pub max_checks: usize,
pub max_mutations: usize,
pub max_watched_keys: usize,
pub max_total_mutation_size_bytes: usize,
pub max_total_key_size_bytes: usize,
}
impl From<KvConfig> for deno_kv::KvConfig {
fn from(value: KvConfig) -> Self {
deno_kv::KvConfigBuilder::default()
.max_write_key_size_bytes(value.max_write_key_size_bytes)
.max_value_size_bytes(value.max_value_size_bytes)
.max_read_ranges(value.max_read_ranges)
.max_read_entries(value.max_read_entries)
.max_checks(value.max_checks)
.max_mutations(value.max_mutations)
.max_watched_keys(value.max_watched_keys)
.max_total_mutation_size_bytes(value.max_total_mutation_size_bytes)
.max_total_key_size_bytes(value.max_total_key_size_bytes)
.build()
}
}
impl Default for KvConfig {
fn default() -> Self {
const MAX_WRITE_KEY_SIZE_BYTES: usize = 2048;
const MAX_VALUE_SIZE_BYTES: usize = 65536;
const MAX_READ_RANGES: usize = 10;
const MAX_READ_ENTRIES: usize = 1000;
const MAX_CHECKS: usize = 100;
const MAX_MUTATIONS: usize = 1000;
const MAX_WATCHED_KEYS: usize = 10;
const MAX_TOTAL_MUTATION_SIZE_BYTES: usize = 800 * 1024;
const MAX_TOTAL_KEY_SIZE_BYTES: usize = 80 * 1024;
KvConfig {
max_write_key_size_bytes: MAX_WRITE_KEY_SIZE_BYTES,
max_value_size_bytes: MAX_VALUE_SIZE_BYTES,
max_read_ranges: MAX_READ_RANGES,
max_read_entries: MAX_READ_ENTRIES,
max_checks: MAX_CHECKS,
max_mutations: MAX_MUTATIONS,
max_watched_keys: MAX_WATCHED_KEYS,
max_total_mutation_size_bytes: MAX_TOTAL_MUTATION_SIZE_BYTES,
max_total_key_size_bytes: MAX_TOTAL_KEY_SIZE_BYTES,
}
}
}
#[derive(Clone)]
pub struct KvStore(KvStoreBuilder, KvConfig);
impl KvStore {
#[must_use]
pub fn new_local(path: Option<PathBuf>, rng_seed: Option<u64>, config: KvConfig) -> Self {
Self(KvStoreBuilder::Local { path, rng_seed }, config)
}
#[must_use]
pub fn new_remote(http_options: deno_kv::remote::HttpOptions, config: KvConfig) -> Self {
Self(KvStoreBuilder::Remote { http_options }, config)
}
#[must_use]
pub fn handler(&self) -> MultiBackendDbHandler {
match &self.0 {
KvStoreBuilder::Local { path, rng_seed } => {
let db = SqliteDbHandler::<PermissionsContainer>::new(path.clone(), *rng_seed);
MultiBackendDbHandler::new(vec![(&[""], Box::new(db))])
}
KvStoreBuilder::Remote { http_options } => {
let db = RemoteDbHandler::<PermissionsContainer>::new(http_options.clone());
MultiBackendDbHandler::new(vec![(&["https://", "http://"], Box::new(db))])
}
}
}
#[must_use]
pub fn config(&self) -> deno_kv::KvConfig {
self.1.into()
}
}
impl Default for KvStore {
fn default() -> Self {
Self::new_local(None, None, KvConfig::default())
}
}
impl SqliteDbHandlerPermissions for PermissionsContainer {
fn check_open<'a>(
&mut self,
path: Cow<'a, std::path::Path>,
open_access: deno_permissions::OpenAccessKind,
api_name: &str,
) -> Result<CheckedPath<'a>, deno_permissions::PermissionCheckError> {
let read = open_access.is_read();
let write = open_access.is_write();
let p = self.0.check_open(true, read, write, path, api_name).ok_or(
PermissionCheckError::PermissionDenied(PermissionDeniedError {
access: api_name.to_string(),
name: "open",
}),
)?;
Ok(CheckedPath::unsafe_new(p))
}
}
impl RemoteDbHandlerPermissions for PermissionsContainer {
fn check_env(&mut self, var: &str) -> Result<(), deno_permissions::PermissionCheckError> {
self.0.check_env(var)?;
Ok(())
}
fn check_net_url(
&mut self,
url: &reqwest::Url,
api_name: &str,
) -> Result<(), deno_permissions::PermissionCheckError> {
self.0.check_url(url, api_name)?;
Ok(())
}
}