#![allow(clippy::result_large_err)]
use deno_core::{error::CoreError, extension, op2};
use crate::permission::CustomPermissions;
use serde::de::DeserializeOwned;
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use std::time::{SystemTime, UNIX_EPOCH};
pub trait StreamItem: DeserializeOwned + 'static {}
impl<T: DeserializeOwned + 'static> StreamItem for T {}
pub struct ChannelStorage<V: StreamItem> {
pub stream_handler: Option<Box<dyn FnMut(V)>>,
}
#[op2(stack_trace)]
#[serde]
fn send_data_to_channel<V: StreamItem>(
#[state] storage: &mut ChannelStorage<V>,
#[serde] data: V,
) -> Result<(), CoreError> {
if let Some(handler) = storage.stream_handler.as_mut() {
(handler)(data);
}
Ok(())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OutputChannel {
StdOut,
StdErr,
}
pub type StdoutHandlerFn = Box<dyn FnMut(&str, OutputChannel)>;
pub struct StdoutHandler(pub Option<StdoutHandlerFn>);
#[derive(Clone)]
struct CacheEntry {
value: serde_json::Value,
expires_at: Option<u64>,
}
#[derive(Clone)]
pub struct SharedCache {
data: Arc<RwLock<HashMap<String, CacheEntry>>>,
}
impl SharedCache {
pub fn new() -> Self {
Self {
data: Arc::new(RwLock::new(HashMap::new())),
}
}
}
impl Default for SharedCache {
fn default() -> Self {
Self::new()
}
}
fn current_timestamp_ms() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("System time should be after UNIX_EPOCH")
.as_millis() as u64
}
#[op2]
#[serde]
fn op_cache_get(
#[state] cache: &SharedCache,
#[string] key: String,
) -> Result<Option<serde_json::Value>, CoreError> {
let mut cache_data = cache
.data
.write()
.expect("Failed to acquire write lock on cache");
if let Some(entry) = cache_data.get(&key) {
if let Some(expires_at) = entry.expires_at {
if current_timestamp_ms() > expires_at {
cache_data.remove(&key);
return Ok(None);
}
}
Ok(Some(entry.value.clone()))
} else {
Ok(None)
}
}
#[op2]
fn op_cache_set(
#[state] cache: &SharedCache,
#[string] key: String,
#[serde] value: serde_json::Value,
#[serde] ttl_ms: Option<u64>,
) -> Result<(), CoreError> {
let expires_at = ttl_ms.map(|ttl| current_timestamp_ms() + ttl);
let entry = CacheEntry { value, expires_at };
cache
.data
.write()
.expect("Failed to acquire write lock on cache")
.insert(key, entry);
Ok(())
}
#[op2(fast)]
fn op_cache_delete(#[state] cache: &SharedCache, #[string] key: String) -> Result<(), CoreError> {
cache
.data
.write()
.expect("Failed to acquire write lock on cache")
.remove(&key);
Ok(())
}
#[op2(fast)]
pub fn op_stream_to_oramacore_print(
#[state] storage: &mut StdoutHandler,
#[string] data: &str,
is_stderr: bool,
) -> Result<(), CoreError> {
if let Some(handler) = storage.0.as_mut() {
let channel = if is_stderr {
OutputChannel::StdErr
} else {
OutputChannel::StdOut
};
(handler)(data, channel);
}
Ok(())
}
extension!(
orama_extension,
deps = [deno_fetch, deno_console],
parameters = [V: StreamItem],
ops = [
send_data_to_channel<V>,
op_stream_to_oramacore_print,
op_cache_get,
op_cache_set,
op_cache_delete,
],
esm_entry_point = "ext:orama_extension/runtime.js",
esm = ["runtime.js"],
options = {
permissions: CustomPermissions,
channel_storage: ChannelStorage<V>,
stdout_handler: StdoutHandler,
shared_cache: SharedCache,
},
state = |state, options| {
state.put::<CustomPermissions>(options.permissions);
state.put::<ChannelStorage<V>>(options.channel_storage);
state.put::<StdoutHandler>(options.stdout_handler);
state.put::<SharedCache>(options.shared_cache);
},
);