├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .secrets.example ├── .vscode └── settings.json ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── example ├── .gitignore ├── Cargo.toml ├── src │ └── lib.rs ├── worker │ ├── metadata_wasm.json │ └── worker.js └── wrangler.example.toml ├── src ├── builder.rs └── lib.rs └── tests ├── .gitignore ├── integration.rs ├── package-lock.json ├── package.json └── worker_kv_test ├── .gitignore ├── .mf-init └── kv │ └── test │ └── simple ├── Cargo.toml ├── src ├── lib.rs └── utils.rs └── wrangler.toml /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: ci 4 | 5 | jobs: 6 | check: 7 | name: Check 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout sources 11 | uses: actions/checkout@v2 12 | - name: Install stable toolchain 13 | uses: actions-rs/toolchain@v1 14 | with: 15 | toolchain: stable 16 | target: wasm32-unknown-unknown 17 | override: true 18 | 19 | - name: Update package repository 20 | run: sudo apt update 21 | - name: Install packages 22 | run: | 23 | sudo apt clean 24 | sudo apt install -y build-essential libssl-dev nodejs 25 | 26 | - name: Install wrangler 27 | run: | 28 | wget https://github.com/cloudflare/wrangler/releases/download/v1.16.1/wrangler-v1.16.1-x86_64-unknown-linux-musl.tar.gz 29 | tar -xzf ./wrangler-v1.16.1-x86_64-unknown-linux-musl.tar.gz 30 | sudo cp ./dist/wrangler /usr/local/bin 31 | 32 | - name: Install miniflare 33 | working-directory: ./tests 34 | run: npm install 35 | 36 | - name: Install wasmpack 37 | run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh 38 | 39 | - name: Check formatting 40 | uses: actions-rs/cargo@v1 41 | with: 42 | command: fmt 43 | args: --all -- --check 44 | 45 | - name: Run cargo check 46 | uses: actions-rs/cargo@v1 47 | with: 48 | command: check 49 | 50 | - name: Run cargo test 51 | uses: actions-rs/cargo@v1 52 | env: 53 | OPENSSL_LIB_DIR: "/usr/lib/x86_64-linux-gnu" 54 | OPENSSL_INCLUDE_DIR: "/usr/include/openssl" 55 | ACCOUNT_ID: ${{ secrets.ACCOUNT_ID }} 56 | KV_ID: ${{ secrets.KV_ID }} 57 | CF_ACCOUNT_ID: ${{ secrets.ACCOUNT_ID }} 58 | CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }} 59 | with: 60 | command: test 61 | args: -- --nocapture 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | .secrets -------------------------------------------------------------------------------- /.secrets.example: -------------------------------------------------------------------------------- 1 | # This file is to be used with https://github.com/nektos/act 2 | ACCOUNT_ID= 3 | KV_ID= 4 | CF_API_TOKEN= -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // Enables rust analyzer in the example project. 3 | "rust-analyzer.linkedProjects": [ 4 | "Cargo.toml", 5 | "example/Cargo.toml", 6 | "tests/worker_kv_test/Cargo.toml" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "worker-kv" 3 | version = "0.6.0" 4 | authors = ["Zeb Piasecki "] 5 | edition = "2018" 6 | description = "Rust bindings to Cloudflare Worker KV Stores." 7 | repository = "https://github.com/zebp/worker-kv" 8 | license = "MIT OR Apache-2.0" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | js-sys = "0.3.50" 14 | serde = { version = "1.0.125", features = ["derive"] } 15 | serde_json = "1.0.64" 16 | thiserror = "1.0.29" 17 | wasm-bindgen = "0.2.84" 18 | wasm-bindgen-futures = "0.4.23" 19 | serde-wasm-bindgen = "0.5.0" 20 | 21 | [dev-dependencies] 22 | fs_extra = "1.2.0" 23 | psutil = { git = "https://github.com/mygnu/rust-psutil", branch = "update-dependencies" } 24 | reqwest = { version = "0.11.8", features = ["json"] } 25 | tokio = { version = "1.5.0", features = [ 26 | "rt", 27 | "macros", 28 | "rt-multi-thread", 29 | "test-util", 30 | "time", 31 | ] } 32 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Zebulon Piasecki 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright 2021 Zebulon Piasecki 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This crate has been moved to the [workers-rs](https://github.com/cloudflare/workers-rs/tree/main/worker-kv) repository. 2 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | wrangler.toml 2 | target/ 3 | worker/generated/ -------------------------------------------------------------------------------- /example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example" 3 | version = "0.1.0" 4 | authors = ["Zeb Piasecki "] 5 | edition = "2018" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "rlib"] 9 | 10 | [dependencies] 11 | js-sys = "0.3.50" 12 | serde_json = "1.0.64" 13 | wasm-bindgen = "0.2.73" 14 | wasm-bindgen-futures = "0.4.23" 15 | worker-kv = { path = "../" } 16 | -------------------------------------------------------------------------------- /example/src/lib.rs: -------------------------------------------------------------------------------- 1 | use js_sys::Promise; 2 | use wasm_bindgen::prelude::*; 3 | use worker_kv::*; 4 | 5 | async fn list() -> Result { 6 | let kv = KvStore::create("EXAMPLE")?; 7 | let list_response = kv.list().limit(100).execute().await?; 8 | 9 | // Returns a pretty printed version of the listed key value pairs. 10 | serde_json::to_string_pretty(&list_response) 11 | .map(Into::into) 12 | .map_err(Into::into) 13 | } 14 | 15 | #[wasm_bindgen] 16 | pub fn start() -> Promise { 17 | wasm_bindgen_futures::future_to_promise(async { list().await.map_err(Into::into) }) 18 | } 19 | -------------------------------------------------------------------------------- /example/worker/metadata_wasm.json: -------------------------------------------------------------------------------- 1 | { 2 | "body_part": "script", 3 | "bindings": [ 4 | { 5 | "name": "wasm", 6 | "type": "wasm_module", 7 | "part": "wasmprogram" 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /example/worker/worker.js: -------------------------------------------------------------------------------- 1 | addEventListener("fetch", (event) => { 2 | event.respondWith(handleRequest(event.request)); 3 | }); 4 | 5 | /** 6 | * Fetch and log a request 7 | * @param {Request} request 8 | */ 9 | async function handleRequest() { 10 | const { start } = wasm_bindgen; 11 | await wasm_bindgen(wasm); 12 | 13 | try { 14 | const text = await start(); 15 | 16 | return new Response(text, { 17 | status: 200, 18 | headers: { 19 | "Content-type": "application/json", 20 | }, 21 | }); 22 | } catch (error) { 23 | return new Response(error, { 24 | status: 500, 25 | }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /example/wrangler.example.toml: -------------------------------------------------------------------------------- 1 | name = "example" 2 | type = "rust" 3 | # Enter your account id 4 | account_id = "" 5 | workers_dev = true 6 | route = "" 7 | zone_id = "" 8 | 9 | # The KV name spaces we will be using in the example 10 | kv_namespaces = [ 11 | { binding = "EXAMPLE", id = "", preview_id = "" }, 12 | ] 13 | -------------------------------------------------------------------------------- /src/builder.rs: -------------------------------------------------------------------------------- 1 | use js_sys::{ArrayBuffer, Function, Object, Promise, Uint8Array}; 2 | use serde::{de::DeserializeOwned, Serialize}; 3 | use serde_json::Value; 4 | use wasm_bindgen::{JsCast, JsValue}; 5 | use wasm_bindgen_futures::JsFuture; 6 | 7 | use crate::{KvError, ListResponse}; 8 | 9 | /// A builder to configure put requests. 10 | #[derive(Debug, Clone, Serialize)] 11 | #[must_use = "PutOptionsBuilder does nothing until you 'execute' it"] 12 | pub struct PutOptionsBuilder { 13 | #[serde(skip)] 14 | pub(crate) this: Object, 15 | #[serde(skip)] 16 | pub(crate) put_function: Function, 17 | #[serde(skip)] 18 | pub(crate) name: JsValue, 19 | #[serde(skip)] 20 | pub(crate) value: JsValue, 21 | #[serde(skip_serializing_if = "Option::is_none")] 22 | pub(crate) expiration: Option, 23 | #[serde(skip_serializing_if = "Option::is_none")] 24 | #[serde(rename = "expirationTtl")] 25 | pub(crate) expiration_ttl: Option, 26 | #[serde(skip_serializing_if = "Option::is_none")] 27 | pub(crate) metadata: Option, 28 | } 29 | 30 | impl PutOptionsBuilder { 31 | /// When (expressed as a [unix timestamp](https://en.wikipedia.org/wiki/Unix_time)) the key 32 | /// value pair will expire in the store. 33 | pub fn expiration(mut self, expiration: u64) -> Self { 34 | self.expiration = Some(expiration); 35 | self 36 | } 37 | /// How many seconds until the key value pair will expire. 38 | pub fn expiration_ttl(mut self, expiration_ttl: u64) -> Self { 39 | self.expiration_ttl = Some(expiration_ttl); 40 | self 41 | } 42 | /// Metadata to be stored with the key value pair. 43 | pub fn metadata(mut self, metadata: T) -> Result { 44 | self.metadata = Some(serde_json::to_value(metadata)?); 45 | Ok(self) 46 | } 47 | /// Puts the value in the kv store. 48 | pub async fn execute(self) -> Result<(), KvError> { 49 | let options_object = serde_wasm_bindgen::to_value(&self).map_err(JsValue::from)?; 50 | let promise: Promise = self 51 | .put_function 52 | .call3(&self.this, &self.name, &self.value, &options_object)? 53 | .into(); 54 | JsFuture::from(promise) 55 | .await 56 | .map(|_| ()) 57 | .map_err(KvError::from) 58 | } 59 | } 60 | 61 | /// A builder to configure list requests. 62 | #[derive(Debug, Clone, Serialize)] 63 | #[must_use = "ListOptionsBuilder does nothing until you 'execute' it"] 64 | pub struct ListOptionsBuilder { 65 | #[serde(skip)] 66 | pub(crate) this: Object, 67 | #[serde(skip)] 68 | pub(crate) list_function: Function, 69 | #[serde(skip_serializing_if = "Option::is_none")] 70 | pub(crate) limit: Option, 71 | #[serde(skip_serializing_if = "Option::is_none")] 72 | pub(crate) cursor: Option, 73 | #[serde(skip_serializing_if = "Option::is_none")] 74 | pub(crate) prefix: Option, 75 | } 76 | 77 | impl ListOptionsBuilder { 78 | /// The maximum number of keys returned. The default is 1000, which is the maximum. It is 79 | /// unlikely that you will want to change this default, but it is included for completeness. 80 | pub fn limit(mut self, limit: u64) -> Self { 81 | self.limit = Some(limit); 82 | self 83 | } 84 | /// A string returned by a previous response used to paginate the keys in the store. 85 | pub fn cursor(mut self, cursor: String) -> Self { 86 | self.cursor = Some(cursor); 87 | self 88 | } 89 | /// A prefix that all keys must start with for them to be included in the response. 90 | pub fn prefix(mut self, prefix: String) -> Self { 91 | self.prefix = Some(prefix); 92 | self 93 | } 94 | /// Lists the key value pairs in the kv store. 95 | pub async fn execute(self) -> Result { 96 | let options_object = serde_wasm_bindgen::to_value(&self).map_err(JsValue::from)?; 97 | let promise: Promise = self 98 | .list_function 99 | .call1(&self.this, &options_object)? 100 | .into(); 101 | 102 | let value = JsFuture::from(promise).await?; 103 | let resp = serde_wasm_bindgen::from_value(value).map_err(JsValue::from)?; 104 | Ok(resp) 105 | } 106 | } 107 | 108 | /// A builder to configure get requests. 109 | #[derive(Debug, Clone, Serialize)] 110 | #[must_use = "GetOptionsBuilder does nothing until you 'get' it"] 111 | pub struct GetOptionsBuilder { 112 | #[serde(skip)] 113 | pub(crate) this: Object, 114 | #[serde(skip)] 115 | pub(crate) get_function: Function, 116 | #[serde(skip)] 117 | pub(crate) get_with_meta_function: Function, 118 | #[serde(skip)] 119 | pub(crate) name: JsValue, 120 | #[serde(rename = "cacheTtl", skip_serializing_if = "Option::is_none")] 121 | pub(crate) cache_ttl: Option, 122 | #[serde(rename = "type", skip_serializing_if = "Option::is_none")] 123 | pub(crate) value_type: Option, 124 | } 125 | 126 | impl GetOptionsBuilder { 127 | /// The cache_ttl parameter must be an integer that is greater than or equal to 60. It defines 128 | /// the length of time in seconds that a KV result is cached in the edge location that it is 129 | /// accessed from. This can be useful for reducing cold read latency on keys that are read 130 | /// relatively infrequently. It is especially useful if your data is write-once or 131 | /// write-rarely, but is not recommended if your data is updated often and you need to see 132 | /// updates shortly after they're written, because writes that happen from other edge locations 133 | /// won't be visible until the cached value expires. 134 | pub fn cache_ttl(mut self, cache_ttl: u64) -> Self { 135 | self.cache_ttl = Some(cache_ttl); 136 | self 137 | } 138 | 139 | fn value_type(mut self, value_type: GetValueType) -> Self { 140 | self.value_type = Some(value_type); 141 | self 142 | } 143 | 144 | async fn get(self) -> Result { 145 | let options_object = serde_wasm_bindgen::to_value(&self).map_err(JsValue::from)?; 146 | let promise: Promise = self 147 | .get_function 148 | .call2(&self.this, &self.name, &options_object)? 149 | .into(); 150 | JsFuture::from(promise).await.map_err(KvError::from) 151 | } 152 | 153 | /// Gets the value as a string. 154 | pub async fn text(self) -> Result, KvError> { 155 | let value = self.value_type(GetValueType::Text).get().await?; 156 | Ok(value.as_string()) 157 | } 158 | 159 | /// Tries to deserialize the inner text to the generic type. 160 | pub async fn json(self) -> Result, KvError> 161 | where 162 | T: DeserializeOwned, 163 | { 164 | let value = self.value_type(GetValueType::Json).get().await?; 165 | Ok(if value.is_null() { 166 | None 167 | } else { 168 | Some(serde_wasm_bindgen::from_value(value).map_err(JsValue::from)?) 169 | }) 170 | } 171 | 172 | /// Gets the value as a byte slice. 173 | pub async fn bytes(self) -> Result>, KvError> { 174 | let v = self.value_type(GetValueType::ArrayBuffer).get().await?; 175 | if ArrayBuffer::instanceof(&v) { 176 | let buffer = ArrayBuffer::from(v); 177 | let buffer = Uint8Array::new(&buffer); 178 | Ok(Some(buffer.to_vec())) 179 | } else { 180 | Ok(None) 181 | } 182 | } 183 | 184 | async fn get_with_metadata(&self) -> Result<(JsValue, Option), KvError> 185 | where 186 | M: DeserializeOwned, 187 | { 188 | let options_object = serde_wasm_bindgen::to_value(&self).map_err(JsValue::from)?; 189 | let promise: Promise = self 190 | .get_with_meta_function 191 | .call2(&self.this, &self.name, &options_object)? 192 | .into(); 193 | 194 | let pair = JsFuture::from(promise).await?; 195 | let metadata = crate::get(&pair, "metadata")?; 196 | let value = crate::get(&pair, "value")?; 197 | 198 | Ok(( 199 | value, 200 | if metadata.is_null() { 201 | None 202 | } else { 203 | Some(serde_wasm_bindgen::from_value(metadata).map_err(JsValue::from)?) 204 | }, 205 | )) 206 | } 207 | 208 | /// Gets the value as a string and it's associated metadata. 209 | pub async fn text_with_metadata(self) -> Result<(Option, Option), KvError> 210 | where 211 | M: DeserializeOwned, 212 | { 213 | let (value, metadata) = self 214 | .value_type(GetValueType::Text) 215 | .get_with_metadata() 216 | .await?; 217 | Ok((value.as_string(), metadata)) 218 | } 219 | 220 | /// Tries to deserialize the inner text to the generic type along with it's metadata. 221 | pub async fn json_with_metadata(self) -> Result<(Option, Option), KvError> 222 | where 223 | T: DeserializeOwned, 224 | M: DeserializeOwned, 225 | { 226 | let (value, metadata) = self 227 | .value_type(GetValueType::Json) 228 | .get_with_metadata() 229 | .await?; 230 | Ok(( 231 | if value.is_null() { 232 | None 233 | } else { 234 | Some(serde_wasm_bindgen::from_value(value).map_err(JsValue::from)?) 235 | }, 236 | metadata, 237 | )) 238 | } 239 | 240 | /// Gets the value as a byte slice and it's associated metadata. 241 | pub async fn bytes_with_metadata(self) -> Result<(Option>, Option), KvError> 242 | where 243 | M: DeserializeOwned, 244 | { 245 | let (value, metadata) = self 246 | .value_type(GetValueType::ArrayBuffer) 247 | .get_with_metadata() 248 | .await?; 249 | 250 | if ArrayBuffer::instanceof(&value) { 251 | let buffer = ArrayBuffer::from(value); 252 | let buffer = Uint8Array::new(&buffer); 253 | Ok((Some(buffer.to_vec()), metadata)) 254 | } else { 255 | Ok((None, metadata)) 256 | } 257 | } 258 | } 259 | 260 | #[derive(Debug, Clone, Serialize)] 261 | #[serde(rename_all = "camelCase")] 262 | pub(crate) enum GetValueType { 263 | Text, 264 | ArrayBuffer, 265 | Json, 266 | } 267 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Bindings to Cloudflare Worker's [KV](https://developers.cloudflare.com/workers/runtime-apis/kv) 2 | //! to be used ***inside*** of a worker's context. 3 | //! 4 | //! # Example 5 | //! ```ignore 6 | //! let kv = KvStore::create("Example")?; 7 | //! 8 | //! // Insert a new entry into the kv. 9 | //! kv.put("example_key", "example_value")? 10 | //! .metadata(vec![1, 2, 3, 4]) // Use some arbitrary serialiazable metadata 11 | //! .execute() 12 | //! .await?; 13 | //! 14 | //! // NOTE: kv changes can take a minute to become visible to other workers. 15 | //! // Get that same metadata. 16 | //! let (value, metadata) = kv.get("example_key").text_with_metadata::>().await?; 17 | //! ``` 18 | #[forbid(missing_docs)] 19 | mod builder; 20 | 21 | pub use builder::*; 22 | 23 | use js_sys::{global, Function, Object, Promise, Reflect, Uint8Array}; 24 | use serde::{Deserialize, Serialize}; 25 | use serde_json::Value; 26 | use wasm_bindgen::JsValue; 27 | use wasm_bindgen_futures::JsFuture; 28 | 29 | /// A binding to a Cloudflare KvStore. 30 | #[derive(Clone)] 31 | pub struct KvStore { 32 | pub(crate) this: Object, 33 | pub(crate) get_function: Function, 34 | pub(crate) get_with_meta_function: Function, 35 | pub(crate) put_function: Function, 36 | pub(crate) list_function: Function, 37 | pub(crate) delete_function: Function, 38 | } 39 | 40 | impl KvStore { 41 | /// Creates a new [`KvStore`] with the binding specified in your `wrangler.toml`. 42 | pub fn create(binding: &str) -> Result { 43 | let this = get(&global(), binding)?; 44 | 45 | // Ensures that the kv store exists. 46 | if this.is_undefined() { 47 | Err(KvError::InvalidKvStore(binding.into())) 48 | } else { 49 | Ok(Self { 50 | get_function: get(&this, "get")?.into(), 51 | get_with_meta_function: get(&this, "getWithMetadata")?.into(), 52 | put_function: get(&this, "put")?.into(), 53 | list_function: get(&this, "list")?.into(), 54 | delete_function: get(&this, "delete")?.into(), 55 | this: this.into(), 56 | }) 57 | } 58 | } 59 | 60 | /// Creates a new [`KvStore`] with the binding specified in your `wrangler.toml`, using an 61 | /// alternative `this` value for arbitrary binding contexts. 62 | pub fn from_this(this: &JsValue, binding: &str) -> Result { 63 | let this = get(this, binding)?; 64 | 65 | // Ensures that the kv store exists. 66 | if this.is_undefined() { 67 | Err(KvError::InvalidKvStore(binding.into())) 68 | } else { 69 | Ok(Self { 70 | get_function: get(&this, "get")?.into(), 71 | get_with_meta_function: get(&this, "getWithMetadata")?.into(), 72 | put_function: get(&this, "put")?.into(), 73 | list_function: get(&this, "list")?.into(), 74 | delete_function: get(&this, "delete")?.into(), 75 | this: this.into(), 76 | }) 77 | } 78 | } 79 | 80 | /// Fetches the value from the kv store by name. 81 | pub fn get(&self, name: &str) -> GetOptionsBuilder { 82 | GetOptionsBuilder { 83 | this: self.this.clone(), 84 | get_function: self.get_function.clone(), 85 | get_with_meta_function: self.get_with_meta_function.clone(), 86 | name: JsValue::from(name), 87 | cache_ttl: None, 88 | value_type: None, 89 | } 90 | } 91 | 92 | /// Puts data into the kv store. 93 | pub fn put(&self, name: &str, value: T) -> Result { 94 | Ok(PutOptionsBuilder { 95 | this: self.this.clone(), 96 | put_function: self.put_function.clone(), 97 | name: JsValue::from(name), 98 | value: value.raw_kv_value()?, 99 | expiration: None, 100 | expiration_ttl: None, 101 | metadata: None, 102 | }) 103 | } 104 | 105 | /// Puts the specified byte slice into the kv store. 106 | pub fn put_bytes(&self, name: &str, value: &[u8]) -> Result { 107 | let typed_array = Uint8Array::new_with_length(value.len() as u32); 108 | typed_array.copy_from(value); 109 | let value: JsValue = typed_array.buffer().into(); 110 | Ok(PutOptionsBuilder { 111 | this: self.this.clone(), 112 | put_function: self.put_function.clone(), 113 | name: JsValue::from(name), 114 | value, 115 | expiration: None, 116 | expiration_ttl: None, 117 | metadata: None, 118 | }) 119 | } 120 | 121 | /// Lists the keys in the kv store. 122 | pub fn list(&self) -> ListOptionsBuilder { 123 | ListOptionsBuilder { 124 | this: self.this.clone(), 125 | list_function: self.list_function.clone(), 126 | limit: None, 127 | cursor: None, 128 | prefix: None, 129 | } 130 | } 131 | 132 | /// Deletes a key in the kv store. 133 | pub async fn delete(&self, name: &str) -> Result<(), KvError> { 134 | let name = JsValue::from(name); 135 | let promise: Promise = self.delete_function.call1(&self.this, &name)?.into(); 136 | JsFuture::from(promise).await?; 137 | Ok(()) 138 | } 139 | } 140 | 141 | /// The response for listing the elements in a KV store. 142 | #[derive(Debug, Clone, Serialize, Deserialize)] 143 | pub struct ListResponse { 144 | /// A slice of all of the keys in the KV store. 145 | pub keys: Vec, 146 | /// If there are more keys that can be fetched using the response's cursor. 147 | pub list_complete: bool, 148 | /// A string used for paginating responses. 149 | pub cursor: Option, 150 | } 151 | 152 | /// The representation of a key in the KV store. 153 | #[derive(Debug, Clone, Serialize, Deserialize)] 154 | pub struct Key { 155 | /// The name of the key. 156 | pub name: String, 157 | /// When (expressed as a [unix timestamp](https://en.wikipedia.org/wiki/Unix_time)) the key 158 | /// value pair will expire in the store. 159 | pub expiration: Option, 160 | /// All metadata associated with the key. 161 | pub metadata: Option, 162 | } 163 | 164 | /// A simple error type that can occur during kv operations. 165 | #[derive(Debug, thiserror::Error)] 166 | pub enum KvError { 167 | #[error("js error: {0:?}")] 168 | JavaScript(JsValue), 169 | #[error("unable to serialize/deserialize: {0}")] 170 | Serialization(serde_json::Error), 171 | #[error("invalid kv store: {0}")] 172 | InvalidKvStore(String), 173 | } 174 | 175 | impl From for JsValue { 176 | fn from(val: KvError) -> Self { 177 | match val { 178 | KvError::JavaScript(value) => value, 179 | KvError::Serialization(e) => format!("KvError::Serialization: {e}").into(), 180 | KvError::InvalidKvStore(binding) => { 181 | format!("KvError::InvalidKvStore: {binding}").into() 182 | } 183 | } 184 | } 185 | } 186 | 187 | impl From for KvError { 188 | fn from(value: JsValue) -> Self { 189 | Self::JavaScript(value) 190 | } 191 | } 192 | 193 | impl From for KvError { 194 | fn from(value: serde_json::Error) -> Self { 195 | Self::Serialization(value) 196 | } 197 | } 198 | 199 | /// A trait for things that can be converted to [`wasm_bindgen::JsValue`] to be passed to the kv. 200 | pub trait ToRawKvValue { 201 | fn raw_kv_value(&self) -> Result; 202 | } 203 | 204 | impl ToRawKvValue for str { 205 | fn raw_kv_value(&self) -> Result { 206 | Ok(JsValue::from(self)) 207 | } 208 | } 209 | 210 | impl ToRawKvValue for T { 211 | fn raw_kv_value(&self) -> Result { 212 | let value = serde_wasm_bindgen::to_value(self).map_err(JsValue::from)?; 213 | 214 | if value.as_string().is_some() { 215 | Ok(value) 216 | } else if let Some(number) = value.as_f64() { 217 | Ok(JsValue::from(number.to_string())) 218 | } else if let Some(boolean) = value.as_bool() { 219 | Ok(JsValue::from(boolean.to_string())) 220 | } else { 221 | js_sys::JSON::stringify(&value) 222 | .map(JsValue::from) 223 | .map_err(Into::into) 224 | } 225 | } 226 | } 227 | 228 | fn get(target: &JsValue, name: &str) -> Result { 229 | Reflect::get(target, &JsValue::from(name)) 230 | } 231 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /tests/integration.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io::{self, ErrorKind}, 3 | net::{SocketAddr, TcpStream}, 4 | path::Path, 5 | process::{Child, Command}, 6 | str::FromStr, 7 | time::{Duration, Instant}, 8 | }; 9 | 10 | use fs_extra::dir::CopyOptions; 11 | use serde::Deserialize; 12 | 13 | #[tokio::test] 14 | #[allow(clippy::needless_collect)] 15 | async fn integration_test() { 16 | let node_procs_to_ignore = psutil::process::processes() 17 | .unwrap() 18 | .into_iter() 19 | .filter_map(|r| r.ok()) 20 | .filter(|process| process.name().unwrap() == "node") 21 | .map(|p| p.pid()) 22 | .collect::>(); 23 | 24 | start_miniflare().expect("unable to spawn miniflare, did you install node modules?"); 25 | wait_for_worker_to_spawn(); 26 | 27 | let endpoints = [ 28 | "get", 29 | "get-not-found", 30 | "list-keys", 31 | "put-simple", 32 | "put-metadata", 33 | "put-expiration", 34 | ]; 35 | 36 | for endpoint in endpoints { 37 | let text_res = reqwest::get(&format!("http://localhost:8787/{}", endpoint)) 38 | .await 39 | .expect("unable to send request") 40 | .text() 41 | .await; 42 | 43 | assert!(text_res.is_ok(), "{} failed", endpoint); 44 | assert_eq!(text_res.unwrap(), "passed".to_string()); 45 | } 46 | 47 | let processes = psutil::process::processes().unwrap(); 48 | 49 | for process in processes.into_iter().filter_map(|r| r.ok()) { 50 | if !node_procs_to_ignore.contains(&process.pid()) && process.name().unwrap() == "node" { 51 | let _ = process.send_signal(psutil::process::Signal::SIGQUIT); 52 | } 53 | } 54 | } 55 | 56 | /// Waits for wrangler to spawn it's http server. 57 | fn wait_for_worker_to_spawn() { 58 | let now = Instant::now(); 59 | let addr = SocketAddr::from_str("0.0.0.0:8787").unwrap(); 60 | 61 | while Instant::now() - now <= Duration::from_secs(5 * 60) { 62 | match TcpStream::connect_timeout(&addr, Duration::from_secs(5)) { 63 | Ok(_) => return, 64 | Err(e) 65 | if e.kind() == ErrorKind::ConnectionRefused 66 | || e.kind() == ErrorKind::ConnectionReset => {} 67 | Err(e) => Err(e).expect("unexpected error connecting to worker"), 68 | } 69 | } 70 | 71 | panic!("timed out connecting to worker") 72 | } 73 | 74 | fn start_miniflare() -> io::Result { 75 | let mf_path = Path::new("tests/worker_kv_test/.mf"); 76 | 77 | if mf_path.exists() { 78 | std::fs::remove_dir_all(mf_path)?; 79 | } 80 | 81 | fs_extra::dir::copy( 82 | "tests/worker_kv_test/.mf-init", 83 | mf_path, 84 | &CopyOptions { 85 | content_only: true, 86 | ..CopyOptions::new() 87 | }, 88 | ) 89 | .unwrap(); 90 | 91 | Command::new("../node_modules/.bin/miniflare") 92 | .args(&["-c", "wrangler.toml", "-k", "test", "--kv-persist"]) 93 | .current_dir("tests/worker_kv_test") 94 | .spawn() 95 | } 96 | 97 | #[derive(Debug, Deserialize)] 98 | enum TestResult { 99 | #[serde(rename = "success")] 100 | Success(String), 101 | #[serde(rename = "failure")] 102 | Failure(String), 103 | } 104 | -------------------------------------------------------------------------------- /tests/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "worker-kv-test-harness", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@cloudflare/workers-types": { 8 | "version": "2.2.2", 9 | "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-2.2.2.tgz", 10 | "integrity": "sha512-kaMn2rueJ0PL1TYVGknTCh0X0x0d9G+FNXAFep7/4uqecEZoQb/63o6rOmMuiqI09zLuHV6xhKRXinokV/MY9A==", 11 | "dev": true 12 | }, 13 | "@iarna/toml": { 14 | "version": "2.2.5", 15 | "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz", 16 | "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==", 17 | "dev": true 18 | }, 19 | "@mrbbot/node-fetch": { 20 | "version": "4.6.0", 21 | "resolved": "https://registry.npmjs.org/@mrbbot/node-fetch/-/node-fetch-4.6.0.tgz", 22 | "integrity": "sha512-GTSOdhpiUnJ9a+XK90NUiqCqOmqXOUU4tqg8WbpZW+nEUTJ4dF3QZ4xhfWg5bqYPagIh/e9r5HxGrftzmulbmw==", 23 | "dev": true, 24 | "requires": { 25 | "@cloudflare/workers-types": "^2.2.2", 26 | "busboy": "^0.3.1", 27 | "formdata-node": "^2.4.0", 28 | "web-streams-polyfill": "^3.0.1" 29 | } 30 | }, 31 | "@nodelib/fs.scandir": { 32 | "version": "2.1.5", 33 | "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", 34 | "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", 35 | "dev": true, 36 | "requires": { 37 | "@nodelib/fs.stat": "2.0.5", 38 | "run-parallel": "^1.1.9" 39 | } 40 | }, 41 | "@nodelib/fs.stat": { 42 | "version": "2.0.5", 43 | "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", 44 | "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", 45 | "dev": true 46 | }, 47 | "@nodelib/fs.walk": { 48 | "version": "1.2.8", 49 | "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", 50 | "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", 51 | "dev": true, 52 | "requires": { 53 | "@nodelib/fs.scandir": "2.1.5", 54 | "fastq": "^1.6.0" 55 | } 56 | }, 57 | "@peculiar/asn1-schema": { 58 | "version": "2.0.38", 59 | "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.0.38.tgz", 60 | "integrity": "sha512-zZ64UpCTm9me15nuCpPgJghSdbEm8atcDQPCyK+bKXjZAQ1735NCZXCSCfbckbQ4MH36Rm9403n/qMq77LFDzQ==", 61 | "dev": true, 62 | "requires": { 63 | "@types/asn1js": "^2.0.2", 64 | "asn1js": "^2.1.1", 65 | "pvtsutils": "^1.2.0", 66 | "tslib": "^2.3.0" 67 | } 68 | }, 69 | "@peculiar/json-schema": { 70 | "version": "1.1.12", 71 | "resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz", 72 | "integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==", 73 | "dev": true, 74 | "requires": { 75 | "tslib": "^2.0.0" 76 | } 77 | }, 78 | "@peculiar/webcrypto": { 79 | "version": "1.1.7", 80 | "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.1.7.tgz", 81 | "integrity": "sha512-aCNLYdHZkvGH+T8/YBOY33jrVGVuLIa3bpizeHXqwN+P4ZtixhA+kxEEWM1amZwUY2nY/iuj+5jdZn/zB7EPPQ==", 82 | "dev": true, 83 | "requires": { 84 | "@peculiar/asn1-schema": "^2.0.32", 85 | "@peculiar/json-schema": "^1.1.12", 86 | "pvtsutils": "^1.1.6", 87 | "tslib": "^2.2.0", 88 | "webcrypto-core": "^1.2.0" 89 | } 90 | }, 91 | "@types/asn1js": { 92 | "version": "2.0.2", 93 | "resolved": "https://registry.npmjs.org/@types/asn1js/-/asn1js-2.0.2.tgz", 94 | "integrity": "sha512-t4YHCgtD+ERvH0FyxvNlYwJ2ezhqw7t+Ygh4urQ7dJER8i185JPv6oIM3ey5YQmGN6Zp9EMbpohkjZi9t3UxwA==", 95 | "dev": true 96 | }, 97 | "@types/node": { 98 | "version": "15.14.9", 99 | "resolved": "https://registry.npmjs.org/@types/node/-/node-15.14.9.tgz", 100 | "integrity": "sha512-qjd88DrCxupx/kJD5yQgZdcYKZKSIGBVDIBE1/LTGcNm3d2Np/jxojkdePDdfnBHJc5W7vSMpbJ1aB7p/Py69A==", 101 | "dev": true 102 | }, 103 | "@types/stack-trace": { 104 | "version": "0.0.29", 105 | "resolved": "https://registry.npmjs.org/@types/stack-trace/-/stack-trace-0.0.29.tgz", 106 | "integrity": "sha512-TgfOX+mGY/NyNxJLIbDWrO9DjGoVSW9+aB8H2yy1fy32jsvxijhmyJI9fDFgvz3YP4lvJaq9DzdR/M1bOgVc9g==", 107 | "dev": true 108 | }, 109 | "@wessberg/stringutil": { 110 | "version": "1.0.19", 111 | "resolved": "https://registry.npmjs.org/@wessberg/stringutil/-/stringutil-1.0.19.tgz", 112 | "integrity": "sha512-9AZHVXWlpN8Cn9k5BC/O0Dzb9E9xfEMXzYrNunwvkUTvuK7xgQPVRZpLo+jWCOZ5r8oBa8NIrHuPEu1hzbb6bg==", 113 | "dev": true 114 | }, 115 | "ansi-regex": { 116 | "version": "5.0.0", 117 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", 118 | "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", 119 | "dev": true 120 | }, 121 | "ansi-styles": { 122 | "version": "4.3.0", 123 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 124 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 125 | "dev": true, 126 | "requires": { 127 | "color-convert": "^2.0.1" 128 | } 129 | }, 130 | "anymatch": { 131 | "version": "3.1.2", 132 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", 133 | "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", 134 | "dev": true, 135 | "requires": { 136 | "normalize-path": "^3.0.0", 137 | "picomatch": "^2.0.4" 138 | } 139 | }, 140 | "asn1js": { 141 | "version": "2.1.1", 142 | "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-2.1.1.tgz", 143 | "integrity": "sha512-t9u0dU0rJN4ML+uxgN6VM2Z4H5jWIYm0w8LsZLzMJaQsgL3IJNbxHgmbWDvJAwspyHpDFuzUaUFh4c05UB4+6g==", 144 | "dev": true, 145 | "requires": { 146 | "pvutils": "^1.0.17" 147 | } 148 | }, 149 | "base64-arraybuffer-es6": { 150 | "version": "0.7.0", 151 | "resolved": "https://registry.npmjs.org/base64-arraybuffer-es6/-/base64-arraybuffer-es6-0.7.0.tgz", 152 | "integrity": "sha512-ESyU/U1CFZDJUdr+neHRhNozeCv72Y7Vm0m1DCbjX3KBjT6eYocvAJlSk6+8+HkVwXlT1FNxhGW6q3UKAlCvvw==", 153 | "dev": true 154 | }, 155 | "binary-extensions": { 156 | "version": "2.2.0", 157 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", 158 | "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", 159 | "dev": true 160 | }, 161 | "braces": { 162 | "version": "3.0.2", 163 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 164 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 165 | "dev": true, 166 | "requires": { 167 | "fill-range": "^7.0.1" 168 | } 169 | }, 170 | "buffer-from": { 171 | "version": "1.1.2", 172 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", 173 | "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", 174 | "dev": true 175 | }, 176 | "busboy": { 177 | "version": "0.3.1", 178 | "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.3.1.tgz", 179 | "integrity": "sha512-y7tTxhGKXcyBxRKAni+awqx8uqaJKrSFSNFSeRG5CsWNdmy2BIK+6VGWEW7TZnIO/533mtMEA4rOevQV815YJw==", 180 | "dev": true, 181 | "requires": { 182 | "dicer": "0.3.0" 183 | } 184 | }, 185 | "chalk": { 186 | "version": "4.1.2", 187 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 188 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 189 | "dev": true, 190 | "requires": { 191 | "ansi-styles": "^4.1.0", 192 | "supports-color": "^7.1.0" 193 | } 194 | }, 195 | "chokidar": { 196 | "version": "3.5.2", 197 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", 198 | "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", 199 | "dev": true, 200 | "requires": { 201 | "anymatch": "~3.1.2", 202 | "braces": "~3.0.2", 203 | "fsevents": "~2.3.2", 204 | "glob-parent": "~5.1.2", 205 | "is-binary-path": "~2.1.0", 206 | "is-glob": "~4.0.1", 207 | "normalize-path": "~3.0.0", 208 | "readdirp": "~3.6.0" 209 | } 210 | }, 211 | "cjstoesm": { 212 | "version": "1.1.4", 213 | "resolved": "https://registry.npmjs.org/cjstoesm/-/cjstoesm-1.1.4.tgz", 214 | "integrity": "sha512-cixLJwK2HS8R8J1jJcYwlrLxWUbdNms5EmVQuvP3O0CGvHNv2WVd2gnqTP/tbTEYzbgWiSYQBZDoAakqsSl94Q==", 215 | "dev": true, 216 | "requires": { 217 | "@wessberg/stringutil": "^1.0.19", 218 | "chalk": "^4.1.1", 219 | "commander": "^7.2.0", 220 | "compatfactory": "^0.0.6", 221 | "crosspath": "^0.0.8", 222 | "fast-glob": "^3.2.5", 223 | "helpertypes": "^0.0.2", 224 | "reserved-words": "^0.1.2", 225 | "resolve": "^1.20.0" 226 | } 227 | }, 228 | "cliui": { 229 | "version": "7.0.4", 230 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", 231 | "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", 232 | "dev": true, 233 | "requires": { 234 | "string-width": "^4.2.0", 235 | "strip-ansi": "^6.0.0", 236 | "wrap-ansi": "^7.0.0" 237 | } 238 | }, 239 | "cluster-key-slot": { 240 | "version": "1.1.0", 241 | "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz", 242 | "integrity": "sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==", 243 | "dev": true 244 | }, 245 | "color-convert": { 246 | "version": "2.0.1", 247 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 248 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 249 | "dev": true, 250 | "requires": { 251 | "color-name": "~1.1.4" 252 | } 253 | }, 254 | "color-name": { 255 | "version": "1.1.4", 256 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 257 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 258 | "dev": true 259 | }, 260 | "commander": { 261 | "version": "7.2.0", 262 | "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", 263 | "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", 264 | "dev": true 265 | }, 266 | "compatfactory": { 267 | "version": "0.0.6", 268 | "resolved": "https://registry.npmjs.org/compatfactory/-/compatfactory-0.0.6.tgz", 269 | "integrity": "sha512-F1LpdNxgxay4UdanmeL75+guJPDg2zu8bFZDVih/kse5hA3oa+aMgvk4tLwq7AFBpy3S0ilnPdSfYsTl/L9NXA==", 270 | "dev": true, 271 | "requires": { 272 | "helpertypes": "^0.0.2" 273 | } 274 | }, 275 | "cookie": { 276 | "version": "0.4.1", 277 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", 278 | "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", 279 | "dev": true 280 | }, 281 | "crosspath": { 282 | "version": "0.0.8", 283 | "resolved": "https://registry.npmjs.org/crosspath/-/crosspath-0.0.8.tgz", 284 | "integrity": "sha512-IKlS3MpP0fhJ50M6ltyLO7Q4NzwfhafpmolMH0EDKyyaY81HutF2mH4hLpCdm3fKZ/TSTW5qPIdTy62YnefEyQ==", 285 | "dev": true, 286 | "requires": { 287 | "@types/node": "^15.6.1" 288 | } 289 | }, 290 | "debug": { 291 | "version": "4.3.2", 292 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", 293 | "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", 294 | "dev": true, 295 | "requires": { 296 | "ms": "2.1.2" 297 | } 298 | }, 299 | "denque": { 300 | "version": "1.5.1", 301 | "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz", 302 | "integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==", 303 | "dev": true 304 | }, 305 | "dicer": { 306 | "version": "0.3.0", 307 | "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz", 308 | "integrity": "sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==", 309 | "dev": true, 310 | "requires": { 311 | "streamsearch": "0.1.2" 312 | } 313 | }, 314 | "dotenv": { 315 | "version": "8.6.0", 316 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", 317 | "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", 318 | "dev": true 319 | }, 320 | "emoji-regex": { 321 | "version": "8.0.0", 322 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 323 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 324 | "dev": true 325 | }, 326 | "env-paths": { 327 | "version": "2.2.1", 328 | "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", 329 | "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", 330 | "dev": true 331 | }, 332 | "escalade": { 333 | "version": "3.1.1", 334 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", 335 | "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", 336 | "dev": true 337 | }, 338 | "event-target-shim": { 339 | "version": "6.0.2", 340 | "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-6.0.2.tgz", 341 | "integrity": "sha512-8q3LsZjRezbFZ2PN+uP+Q7pnHUMmAOziU2vA2OwoFaKIXxlxl38IylhSSgUorWu/rf4er67w0ikBqjBFk/pomA==", 342 | "dev": true 343 | }, 344 | "fast-glob": { 345 | "version": "3.2.7", 346 | "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", 347 | "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", 348 | "dev": true, 349 | "requires": { 350 | "@nodelib/fs.stat": "^2.0.2", 351 | "@nodelib/fs.walk": "^1.2.3", 352 | "glob-parent": "^5.1.2", 353 | "merge2": "^1.3.0", 354 | "micromatch": "^4.0.4" 355 | } 356 | }, 357 | "fastq": { 358 | "version": "1.13.0", 359 | "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", 360 | "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", 361 | "dev": true, 362 | "requires": { 363 | "reusify": "^1.0.4" 364 | } 365 | }, 366 | "fill-range": { 367 | "version": "7.0.1", 368 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 369 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 370 | "dev": true, 371 | "requires": { 372 | "to-regex-range": "^5.0.1" 373 | } 374 | }, 375 | "formdata-node": { 376 | "version": "2.5.0", 377 | "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-2.5.0.tgz", 378 | "integrity": "sha512-JFSNLq34u2Tqc6F034x5aaK3ksIfrDBMPie8b4KYx2/pVDLxWFXDly52dsvHjZ+A0LGHTZb/w4HBZVdgN74RTw==", 379 | "dev": true, 380 | "requires": { 381 | "mime-types": "2.1.29" 382 | } 383 | }, 384 | "fsevents": { 385 | "version": "2.3.2", 386 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 387 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 388 | "dev": true, 389 | "optional": true 390 | }, 391 | "function-bind": { 392 | "version": "1.1.1", 393 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 394 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 395 | "dev": true 396 | }, 397 | "get-caller-file": { 398 | "version": "2.0.5", 399 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 400 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", 401 | "dev": true 402 | }, 403 | "glob-parent": { 404 | "version": "5.1.2", 405 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 406 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 407 | "dev": true, 408 | "requires": { 409 | "is-glob": "^4.0.1" 410 | } 411 | }, 412 | "has": { 413 | "version": "1.0.3", 414 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 415 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 416 | "dev": true, 417 | "requires": { 418 | "function-bind": "^1.1.1" 419 | } 420 | }, 421 | "has-flag": { 422 | "version": "4.0.0", 423 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 424 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 425 | "dev": true 426 | }, 427 | "helpertypes": { 428 | "version": "0.0.2", 429 | "resolved": "https://registry.npmjs.org/helpertypes/-/helpertypes-0.0.2.tgz", 430 | "integrity": "sha512-PKVtWnJ+dcvPeUJRiqtbraN/Hr2rNEnS14T/IxDBb0KgHkAL5w4YwVxMEPowA9vyoMP0DrwO0TxJ+KH3UF/6YA==", 431 | "dev": true 432 | }, 433 | "html-rewriter-wasm": { 434 | "version": "0.3.2", 435 | "resolved": "https://registry.npmjs.org/html-rewriter-wasm/-/html-rewriter-wasm-0.3.2.tgz", 436 | "integrity": "sha512-b+pOh+bs00uRVNIZoTgGBREjUKN47pchTNwkxKuP4ecQTFcOA6KJIW+jjvjjXrkSRURZsideLxFKqX7hnxdegQ==", 437 | "dev": true 438 | }, 439 | "http-cache-semantics": { 440 | "version": "4.1.0", 441 | "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", 442 | "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", 443 | "dev": true 444 | }, 445 | "ioredis": { 446 | "version": "4.27.9", 447 | "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-4.27.9.tgz", 448 | "integrity": "sha512-hAwrx9F+OQ0uIvaJefuS3UTqW+ByOLyLIV+j0EH8ClNVxvFyH9Vmb08hCL4yje6mDYT5zMquShhypkd50RRzkg==", 449 | "dev": true, 450 | "requires": { 451 | "cluster-key-slot": "^1.1.0", 452 | "debug": "^4.3.1", 453 | "denque": "^1.1.0", 454 | "lodash.defaults": "^4.2.0", 455 | "lodash.flatten": "^4.4.0", 456 | "lodash.isarguments": "^3.1.0", 457 | "p-map": "^2.1.0", 458 | "redis-commands": "1.7.0", 459 | "redis-errors": "^1.2.0", 460 | "redis-parser": "^3.0.0", 461 | "standard-as-callback": "^2.1.0" 462 | } 463 | }, 464 | "is-binary-path": { 465 | "version": "2.1.0", 466 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 467 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 468 | "dev": true, 469 | "requires": { 470 | "binary-extensions": "^2.0.0" 471 | } 472 | }, 473 | "is-core-module": { 474 | "version": "2.6.0", 475 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.6.0.tgz", 476 | "integrity": "sha512-wShG8vs60jKfPWpF2KZRaAtvt3a20OAn7+IJ6hLPECpSABLcKtFKTTI4ZtH5QcBruBHlq+WsdHWyz0BCZW7svQ==", 477 | "dev": true, 478 | "requires": { 479 | "has": "^1.0.3" 480 | } 481 | }, 482 | "is-extglob": { 483 | "version": "2.1.1", 484 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 485 | "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", 486 | "dev": true 487 | }, 488 | "is-fullwidth-code-point": { 489 | "version": "3.0.0", 490 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 491 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 492 | "dev": true 493 | }, 494 | "is-glob": { 495 | "version": "4.0.1", 496 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", 497 | "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", 498 | "dev": true, 499 | "requires": { 500 | "is-extglob": "^2.1.1" 501 | } 502 | }, 503 | "is-number": { 504 | "version": "7.0.0", 505 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 506 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 507 | "dev": true 508 | }, 509 | "kleur": { 510 | "version": "4.1.4", 511 | "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.4.tgz", 512 | "integrity": "sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==", 513 | "dev": true 514 | }, 515 | "lodash": { 516 | "version": "4.17.21", 517 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 518 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", 519 | "dev": true 520 | }, 521 | "lodash.defaults": { 522 | "version": "4.2.0", 523 | "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", 524 | "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=", 525 | "dev": true 526 | }, 527 | "lodash.flatten": { 528 | "version": "4.4.0", 529 | "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", 530 | "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=", 531 | "dev": true 532 | }, 533 | "lodash.isarguments": { 534 | "version": "3.1.0", 535 | "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", 536 | "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", 537 | "dev": true 538 | }, 539 | "merge2": { 540 | "version": "1.4.1", 541 | "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", 542 | "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", 543 | "dev": true 544 | }, 545 | "micromatch": { 546 | "version": "4.0.4", 547 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", 548 | "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", 549 | "dev": true, 550 | "requires": { 551 | "braces": "^3.0.1", 552 | "picomatch": "^2.2.3" 553 | } 554 | }, 555 | "mime-db": { 556 | "version": "1.46.0", 557 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.46.0.tgz", 558 | "integrity": "sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ==", 559 | "dev": true 560 | }, 561 | "mime-types": { 562 | "version": "2.1.29", 563 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.29.tgz", 564 | "integrity": "sha512-Y/jMt/S5sR9OaqteJtslsFZKWOIIqMACsJSiHghlCAyhf7jfVYjKBmLiX8OgpWeW+fjJ2b+Az69aPFPkUOY6xQ==", 565 | "dev": true, 566 | "requires": { 567 | "mime-db": "1.46.0" 568 | } 569 | }, 570 | "miniflare": { 571 | "version": "1.4.1", 572 | "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-1.4.1.tgz", 573 | "integrity": "sha512-hJkMbTEM+sSiAo2yuPOucrdFYINLU7vvl9uVkRzAQ/h0CjmkYOCoyBn4jYzWtDZeQ0XrkyS6PGUCO277B5TsXA==", 574 | "dev": true, 575 | "requires": { 576 | "@iarna/toml": "^2.2.5", 577 | "@mrbbot/node-fetch": "^4.6.0", 578 | "@peculiar/webcrypto": "^1.1.4", 579 | "chokidar": "^3.5.1", 580 | "cjstoesm": "^1.1.4", 581 | "dotenv": "^8.2.0", 582 | "env-paths": "^2.2.1", 583 | "event-target-shim": "^6.0.2", 584 | "formdata-node": "^2.5.0", 585 | "html-rewriter-wasm": "^0.3.2", 586 | "http-cache-semantics": "^4.1.0", 587 | "ioredis": "^4.27.6", 588 | "kleur": "^4.1.4", 589 | "node-cron": "^2.0.3", 590 | "picomatch": "^2.3.0", 591 | "sanitize-filename": "^1.6.3", 592 | "selfsigned": "^1.10.11", 593 | "semiver": "^1.1.0", 594 | "source-map-support": "^0.5.19", 595 | "tslib": "^2.3.0", 596 | "typescript": "^4.3.4", 597 | "typeson": "^6.1.0", 598 | "typeson-registry": "^1.0.0-alpha.39", 599 | "web-streams-polyfill": "^3.1.0", 600 | "ws": "^7.5.0", 601 | "yargs": "^16.2.0", 602 | "youch": "^2.2.2" 603 | } 604 | }, 605 | "ms": { 606 | "version": "2.1.2", 607 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 608 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 609 | "dev": true 610 | }, 611 | "mustache": { 612 | "version": "4.2.0", 613 | "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", 614 | "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", 615 | "dev": true 616 | }, 617 | "node-cron": { 618 | "version": "2.0.3", 619 | "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-2.0.3.tgz", 620 | "integrity": "sha512-eJI+QitXlwcgiZwNNSRbqsjeZMp5shyajMR81RZCqeW0ZDEj4zU9tpd4nTh/1JsBiKbF8d08FCewiipDmVIYjg==", 621 | "dev": true, 622 | "requires": { 623 | "opencollective-postinstall": "^2.0.0", 624 | "tz-offset": "0.0.1" 625 | } 626 | }, 627 | "node-forge": { 628 | "version": "0.10.0", 629 | "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", 630 | "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", 631 | "dev": true 632 | }, 633 | "normalize-path": { 634 | "version": "3.0.0", 635 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 636 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 637 | "dev": true 638 | }, 639 | "opencollective-postinstall": { 640 | "version": "2.0.3", 641 | "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz", 642 | "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==", 643 | "dev": true 644 | }, 645 | "p-map": { 646 | "version": "2.1.0", 647 | "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", 648 | "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", 649 | "dev": true 650 | }, 651 | "path-parse": { 652 | "version": "1.0.7", 653 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 654 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", 655 | "dev": true 656 | }, 657 | "picomatch": { 658 | "version": "2.3.0", 659 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", 660 | "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", 661 | "dev": true 662 | }, 663 | "punycode": { 664 | "version": "2.1.1", 665 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 666 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", 667 | "dev": true 668 | }, 669 | "pvtsutils": { 670 | "version": "1.2.0", 671 | "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.2.0.tgz", 672 | "integrity": "sha512-IDefMJEQl7HX0FP2hIKJFnAR11klP1js2ixCrOaMhe3kXFK6RQ2ABUCuwWaaD4ib0hSbh2fGTICvWJJhDfNecA==", 673 | "dev": true, 674 | "requires": { 675 | "tslib": "^2.2.0" 676 | } 677 | }, 678 | "pvutils": { 679 | "version": "1.0.17", 680 | "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.0.17.tgz", 681 | "integrity": "sha512-wLHYUQxWaXVQvKnwIDWFVKDJku9XDCvyhhxoq8dc5MFdIlRenyPI9eSfEtcvgHgD7FlvCyGAlWgOzRnZD99GZQ==", 682 | "dev": true 683 | }, 684 | "queue-microtask": { 685 | "version": "1.2.3", 686 | "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", 687 | "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", 688 | "dev": true 689 | }, 690 | "readdirp": { 691 | "version": "3.6.0", 692 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", 693 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", 694 | "dev": true, 695 | "requires": { 696 | "picomatch": "^2.2.1" 697 | } 698 | }, 699 | "redis-commands": { 700 | "version": "1.7.0", 701 | "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz", 702 | "integrity": "sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ==", 703 | "dev": true 704 | }, 705 | "redis-errors": { 706 | "version": "1.2.0", 707 | "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", 708 | "integrity": "sha1-62LSrbFeTq9GEMBK/hUpOEJQq60=", 709 | "dev": true 710 | }, 711 | "redis-parser": { 712 | "version": "3.0.0", 713 | "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", 714 | "integrity": "sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=", 715 | "dev": true, 716 | "requires": { 717 | "redis-errors": "^1.0.0" 718 | } 719 | }, 720 | "require-directory": { 721 | "version": "2.1.1", 722 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 723 | "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", 724 | "dev": true 725 | }, 726 | "reserved-words": { 727 | "version": "0.1.2", 728 | "resolved": "https://registry.npmjs.org/reserved-words/-/reserved-words-0.1.2.tgz", 729 | "integrity": "sha1-AKCUD5jNUBrqqsMWQR2a3FKzGrE=", 730 | "dev": true 731 | }, 732 | "resolve": { 733 | "version": "1.20.0", 734 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", 735 | "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", 736 | "dev": true, 737 | "requires": { 738 | "is-core-module": "^2.2.0", 739 | "path-parse": "^1.0.6" 740 | } 741 | }, 742 | "reusify": { 743 | "version": "1.0.4", 744 | "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", 745 | "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", 746 | "dev": true 747 | }, 748 | "run-parallel": { 749 | "version": "1.2.0", 750 | "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", 751 | "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", 752 | "dev": true, 753 | "requires": { 754 | "queue-microtask": "^1.2.2" 755 | } 756 | }, 757 | "sanitize-filename": { 758 | "version": "1.6.3", 759 | "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz", 760 | "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==", 761 | "dev": true, 762 | "requires": { 763 | "truncate-utf8-bytes": "^1.0.0" 764 | } 765 | }, 766 | "selfsigned": { 767 | "version": "1.10.11", 768 | "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.11.tgz", 769 | "integrity": "sha512-aVmbPOfViZqOZPgRBT0+3u4yZFHpmnIghLMlAcb5/xhp5ZtB/RVnKhz5vl2M32CLXAqR4kha9zfhNg0Lf/sxKA==", 770 | "dev": true, 771 | "requires": { 772 | "node-forge": "^0.10.0" 773 | } 774 | }, 775 | "semiver": { 776 | "version": "1.1.0", 777 | "resolved": "https://registry.npmjs.org/semiver/-/semiver-1.1.0.tgz", 778 | "integrity": "sha512-QNI2ChmuioGC1/xjyYwyZYADILWyW6AmS1UH6gDj/SFUUUS4MBAWs/7mxnkRPc/F4iHezDP+O8t0dO8WHiEOdg==", 779 | "dev": true 780 | }, 781 | "source-map": { 782 | "version": "0.6.1", 783 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 784 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 785 | "dev": true 786 | }, 787 | "source-map-support": { 788 | "version": "0.5.20", 789 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz", 790 | "integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==", 791 | "dev": true, 792 | "requires": { 793 | "buffer-from": "^1.0.0", 794 | "source-map": "^0.6.0" 795 | } 796 | }, 797 | "stack-trace": { 798 | "version": "0.0.10", 799 | "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", 800 | "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", 801 | "dev": true 802 | }, 803 | "standard-as-callback": { 804 | "version": "2.1.0", 805 | "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", 806 | "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==", 807 | "dev": true 808 | }, 809 | "streamsearch": { 810 | "version": "0.1.2", 811 | "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", 812 | "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=", 813 | "dev": true 814 | }, 815 | "string-width": { 816 | "version": "4.2.2", 817 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", 818 | "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", 819 | "dev": true, 820 | "requires": { 821 | "emoji-regex": "^8.0.0", 822 | "is-fullwidth-code-point": "^3.0.0", 823 | "strip-ansi": "^6.0.0" 824 | } 825 | }, 826 | "strip-ansi": { 827 | "version": "6.0.0", 828 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", 829 | "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", 830 | "dev": true, 831 | "requires": { 832 | "ansi-regex": "^5.0.0" 833 | } 834 | }, 835 | "supports-color": { 836 | "version": "7.2.0", 837 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 838 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 839 | "dev": true, 840 | "requires": { 841 | "has-flag": "^4.0.0" 842 | } 843 | }, 844 | "to-regex-range": { 845 | "version": "5.0.1", 846 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 847 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 848 | "dev": true, 849 | "requires": { 850 | "is-number": "^7.0.0" 851 | } 852 | }, 853 | "tr46": { 854 | "version": "2.1.0", 855 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", 856 | "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", 857 | "dev": true, 858 | "requires": { 859 | "punycode": "^2.1.1" 860 | } 861 | }, 862 | "truncate-utf8-bytes": { 863 | "version": "1.0.2", 864 | "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", 865 | "integrity": "sha1-QFkjkJWS1W94pYGENLC3hInKXys=", 866 | "dev": true, 867 | "requires": { 868 | "utf8-byte-length": "^1.0.1" 869 | } 870 | }, 871 | "tslib": { 872 | "version": "2.3.1", 873 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", 874 | "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", 875 | "dev": true 876 | }, 877 | "typescript": { 878 | "version": "4.4.3", 879 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.3.tgz", 880 | "integrity": "sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA==", 881 | "dev": true 882 | }, 883 | "typeson": { 884 | "version": "6.1.0", 885 | "resolved": "https://registry.npmjs.org/typeson/-/typeson-6.1.0.tgz", 886 | "integrity": "sha512-6FTtyGr8ldU0pfbvW/eOZrEtEkczHRUtduBnA90Jh9kMPCiFNnXIon3vF41N0S4tV1HHQt4Hk1j4srpESziCaA==", 887 | "dev": true 888 | }, 889 | "typeson-registry": { 890 | "version": "1.0.0-alpha.39", 891 | "resolved": "https://registry.npmjs.org/typeson-registry/-/typeson-registry-1.0.0-alpha.39.tgz", 892 | "integrity": "sha512-NeGDEquhw+yfwNhguLPcZ9Oj0fzbADiX4R0WxvoY8nGhy98IbzQy1sezjoEFWOywOboj/DWehI+/aUlRVrJnnw==", 893 | "dev": true, 894 | "requires": { 895 | "base64-arraybuffer-es6": "^0.7.0", 896 | "typeson": "^6.0.0", 897 | "whatwg-url": "^8.4.0" 898 | } 899 | }, 900 | "tz-offset": { 901 | "version": "0.0.1", 902 | "resolved": "https://registry.npmjs.org/tz-offset/-/tz-offset-0.0.1.tgz", 903 | "integrity": "sha512-kMBmblijHJXyOpKzgDhKx9INYU4u4E1RPMB0HqmKSgWG8vEcf3exEfLh4FFfzd3xdQOw9EuIy/cP0akY6rHopQ==", 904 | "dev": true 905 | }, 906 | "utf8-byte-length": { 907 | "version": "1.0.4", 908 | "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz", 909 | "integrity": "sha1-9F8VDExm7uloGGUFq5P8u4rWv2E=", 910 | "dev": true 911 | }, 912 | "web-streams-polyfill": { 913 | "version": "3.1.1", 914 | "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.1.1.tgz", 915 | "integrity": "sha512-Czi3fG883e96T4DLEPRvufrF2ydhOOW1+1a6c3gNjH2aIh50DNFBdfwh2AKoOf1rXvpvavAoA11Qdq9+BKjE0Q==", 916 | "dev": true 917 | }, 918 | "webcrypto-core": { 919 | "version": "1.2.1", 920 | "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.2.1.tgz", 921 | "integrity": "sha512-5+h1/e/A4eegCRTg+oQ9ehTJRTMwFhZazJ2RH1FP0VC3q1/0xl7x6SzzTwPxd/VTGc7kjuSEJGnfNgoLe5jNRQ==", 922 | "dev": true, 923 | "requires": { 924 | "@peculiar/asn1-schema": "^2.0.38", 925 | "@peculiar/json-schema": "^1.1.12", 926 | "asn1js": "^2.1.1", 927 | "pvtsutils": "^1.2.0", 928 | "tslib": "^2.3.1" 929 | } 930 | }, 931 | "webidl-conversions": { 932 | "version": "6.1.0", 933 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", 934 | "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", 935 | "dev": true 936 | }, 937 | "whatwg-url": { 938 | "version": "8.7.0", 939 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", 940 | "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", 941 | "dev": true, 942 | "requires": { 943 | "lodash": "^4.7.0", 944 | "tr46": "^2.1.0", 945 | "webidl-conversions": "^6.1.0" 946 | } 947 | }, 948 | "wrap-ansi": { 949 | "version": "7.0.0", 950 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 951 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 952 | "dev": true, 953 | "requires": { 954 | "ansi-styles": "^4.0.0", 955 | "string-width": "^4.1.0", 956 | "strip-ansi": "^6.0.0" 957 | } 958 | }, 959 | "ws": { 960 | "version": "7.5.5", 961 | "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.5.tgz", 962 | "integrity": "sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==", 963 | "dev": true 964 | }, 965 | "y18n": { 966 | "version": "5.0.8", 967 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", 968 | "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", 969 | "dev": true 970 | }, 971 | "yargs": { 972 | "version": "16.2.0", 973 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", 974 | "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", 975 | "dev": true, 976 | "requires": { 977 | "cliui": "^7.0.2", 978 | "escalade": "^3.1.1", 979 | "get-caller-file": "^2.0.5", 980 | "require-directory": "^2.1.1", 981 | "string-width": "^4.2.0", 982 | "y18n": "^5.0.5", 983 | "yargs-parser": "^20.2.2" 984 | } 985 | }, 986 | "yargs-parser": { 987 | "version": "20.2.9", 988 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", 989 | "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", 990 | "dev": true 991 | }, 992 | "youch": { 993 | "version": "2.2.2", 994 | "resolved": "https://registry.npmjs.org/youch/-/youch-2.2.2.tgz", 995 | "integrity": "sha512-/FaCeG3GkuJwaMR34GHVg0l8jCbafZLHiFowSjqLlqhC6OMyf2tPJBu8UirF7/NI9X/R5ai4QfEKUCOxMAGxZQ==", 996 | "dev": true, 997 | "requires": { 998 | "@types/stack-trace": "0.0.29", 999 | "cookie": "^0.4.1", 1000 | "mustache": "^4.2.0", 1001 | "stack-trace": "0.0.10" 1002 | } 1003 | } 1004 | } 1005 | } 1006 | -------------------------------------------------------------------------------- /tests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "worker-kv-test-harness", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": {}, 7 | "keywords": [], 8 | "author": "", 9 | "license": "ISC", 10 | "devDependencies": { 11 | "miniflare": "^1.4.1" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/worker_kv_test/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | wasm-pack.log 4 | build/ 5 | .mf/ -------------------------------------------------------------------------------- /tests/worker_kv_test/.mf-init/kv/test/simple: -------------------------------------------------------------------------------- 1 | passed -------------------------------------------------------------------------------- /tests/worker_kv_test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "worker_kv_test" 3 | version = "0.1.0" 4 | authors = ["Zeb Piasecki "] 5 | edition = "2018" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "rlib"] 9 | 10 | [features] 11 | default = ["console_error_panic_hook"] 12 | 13 | [dependencies] 14 | cfg-if = "0.1.2" 15 | worker = "0.0.13" 16 | worker-kv = { path = "../../" } 17 | serde_json = "1.0.67" 18 | 19 | # The `console_error_panic_hook` crate provides better debugging of panics by 20 | # logging them with `console.error`. This is great for development, but requires 21 | # all the `std::fmt` and `std::panicking` infrastructure, so isn't great for 22 | # code size when deploying. 23 | console_error_panic_hook = { version = "0.1.1", optional = true } 24 | 25 | # `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size 26 | # compared to the default allocator's ~10K. It is slower than the default 27 | # allocator, however. 28 | wee_alloc = { version = "0.4.2", optional = true } 29 | thiserror = "1.0.29" 30 | 31 | [profile.release] 32 | # Tell `rustc` to optimize for small code size. 33 | opt-level = "s" 34 | -------------------------------------------------------------------------------- /tests/worker_kv_test/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | 3 | use worker::*; 4 | use worker_kv::{KvError, KvStore}; 5 | 6 | type TestResult = std::result::Result; 7 | 8 | mod utils; 9 | 10 | macro_rules! kv_assert_eq { 11 | ($left: expr, $right: expr) => {{ 12 | let left = &$left; 13 | let right = &$right; 14 | if left != right { 15 | Err(TestError::Other(format!("{:#?} != {:#?}", left, right))) 16 | } else { 17 | Ok(()) 18 | } 19 | }}; 20 | } 21 | 22 | #[event(fetch)] 23 | pub async fn main(req: Request, env: Env, _ctx: Context) -> Result { 24 | // Optionally, get more helpful error messages written to the console in the case of a panic. 25 | utils::set_panic_hook(); 26 | 27 | // Create the KV store directly from `worker_kv` as the rust worker sdk uses a published version. 28 | let store = KvStore::from_this(&env, "test").expect("test kv store not bound"); 29 | 30 | Router::with_data(store) 31 | .get_async("/get", |req, ctx| wrap(req, ctx, get)) 32 | .get_async("/get-not-found", |req, ctx| wrap(req, ctx, get_not_found)) 33 | .get_async("/list-keys", |req, ctx| wrap(req, ctx, list_keys)) 34 | .get_async("/put-simple", |req, ctx| wrap(req, ctx, put_simple)) 35 | .get_async("/put-metadata", |req, ctx| wrap(req, ctx, put_metadata)) 36 | .get_async("/put-expiration", |req, ctx| wrap(req, ctx, put_expiration)) 37 | .run(req, env) 38 | .await 39 | } 40 | 41 | async fn get(_: Request, ctx: RouteContext) -> TestResult { 42 | let store = ctx.data; 43 | store 44 | .get("simple") 45 | .text() 46 | .await 47 | .map_err(TestError::from) 48 | .and_then(|v| match v { 49 | Some(e) => Ok(e), 50 | None => Err(TestError::Other("no value found".into())), 51 | }) 52 | } 53 | 54 | async fn get_not_found(_: Request, ctx: RouteContext) -> TestResult { 55 | let store = ctx.data; 56 | let value = store.get("not_found").text().await; 57 | 58 | value.map_err(TestError::from).and_then(|v| match v { 59 | Some(_) => Err(TestError::Other("unexpected value present".into())), 60 | None => Ok("passed".into()), 61 | }) 62 | } 63 | 64 | async fn list_keys(_: Request, ctx: RouteContext) -> TestResult { 65 | let store = ctx.data; 66 | let list_res = store.list().execute().await?; 67 | 68 | // TODO: Test cursor and things. 69 | kv_assert_eq!(list_res.keys.len(), 1)?; 70 | 71 | Ok("passed".into()) 72 | } 73 | 74 | async fn put_simple(_: Request, ctx: RouteContext) -> TestResult { 75 | let store = ctx.data; 76 | store.put("put_a", "test")?.execute().await?; 77 | 78 | let val = store.get("put_a").text().await?.unwrap(); 79 | kv_assert_eq!(val, "test")?; 80 | 81 | Ok("passed".into()) 82 | } 83 | 84 | async fn put_metadata(_: Request, ctx: RouteContext) -> TestResult { 85 | let store = ctx.data; 86 | store.put("put_b", "test")?.metadata(100)?.execute().await?; 87 | 88 | let (val, meta) = store.get("put_b").text_with_metadata::().await?; 89 | kv_assert_eq!(val.unwrap(), "test")?; 90 | kv_assert_eq!(meta.unwrap(), 100)?; 91 | 92 | Ok("passed".into()) 93 | } 94 | 95 | async fn put_expiration(_: Request, ctx: RouteContext) -> TestResult { 96 | const EXPIRATION: u64 = 2000000000; 97 | let store = ctx.data; 98 | store 99 | .put("put_c", "test")? 100 | .expiration(EXPIRATION) 101 | .execute() 102 | .await?; 103 | 104 | let val = store.get("put_a").text().await?.unwrap(); 105 | kv_assert_eq!(val, "test")?; 106 | 107 | let list = store.list().prefix("put_c".into()).execute().await?; 108 | let key = list 109 | .keys 110 | .into_iter() 111 | .find(|key| key.name == "put_c") 112 | .unwrap(); 113 | kv_assert_eq!(key.expiration, Some(EXPIRATION))?; 114 | 115 | Ok("passed".into()) 116 | } 117 | 118 | async fn wrap( 119 | req: Request, 120 | ctx: RouteContext, 121 | func: fn(Request, RouteContext) -> T, 122 | ) -> Result 123 | where 124 | T: Future + 'static, 125 | { 126 | let result = func(req, ctx); 127 | 128 | match result.await { 129 | Ok(value) => Response::ok(value), 130 | Err(e) => Response::ok(e.to_string()).map(|res| res.with_status(500)), 131 | } 132 | } 133 | 134 | #[derive(Debug, thiserror::Error)] 135 | enum TestError { 136 | #[error("{0}")] 137 | Kv(#[from] KvError), 138 | #[error("{0}")] 139 | Other(String), 140 | } 141 | -------------------------------------------------------------------------------- /tests/worker_kv_test/src/utils.rs: -------------------------------------------------------------------------------- 1 | use cfg_if::cfg_if; 2 | 3 | cfg_if! { 4 | // https://github.com/rustwasm/console_error_panic_hook#readme 5 | if #[cfg(feature = "console_error_panic_hook")] { 6 | extern crate console_error_panic_hook; 7 | pub use self::console_error_panic_hook::set_once as set_panic_hook; 8 | } else { 9 | #[inline] 10 | pub fn set_panic_hook() {} 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/worker_kv_test/wrangler.toml: -------------------------------------------------------------------------------- 1 | name = "worker_kv_test" 2 | type = "rust" 3 | workers_dev = true 4 | compatibility_date = "2021-08-27" # required 5 | compatibility_flags = [ "formdata_parser_supports_files" ] # required 6 | 7 | [vars] 8 | WORKERS_RS_VERSION = "0.0.4" 9 | 10 | [build] 11 | command = "cargo install -q worker-build && worker-build --release" # required 12 | 13 | [build.upload] 14 | dir = "build/worker" 15 | format = "modules" 16 | main = "./shim.mjs" 17 | 18 | [[build.upload.rules]] 19 | globs = ["**/*.wasm"] 20 | type = "CompiledWasm" 21 | 22 | # read more about configuring your Worker via wrangler.toml at: 23 | # https://developers.cloudflare.com/workers/cli-wrangler/configuration 24 | --------------------------------------------------------------------------------