├── .travis.yml ├── .gitignore ├── CHANGELOG.md ├── test └── glittershark │ ├── core_async_storage │ └── test_runner.cljs │ ├── core_async_storage_test.clj │ └── core_async_storage_test.cljs ├── package.json ├── LICENSE ├── project.clj ├── src └── glittershark │ ├── core_async_storage.clj │ └── core_async_storage.cljs └── README.md /.travis.yml: -------------------------------------------------------------------------------- 1 | language: clojure 2 | sudo: false 3 | script: 4 | - lein test 5 | - lein doo phantom test once 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | .cljs_rhino_repl 11 | node_modules 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. This change 3 | log follows the conventions of [keepachangelog.com](http://keepachangelog.com/). 4 | -------------------------------------------------------------------------------- /test/glittershark/core_async_storage/test_runner.cljs: -------------------------------------------------------------------------------- 1 | (ns glittershark.core-async-storage.test-runner 2 | (:require [doo.runner :refer-macros [doo-tests]] 3 | [glittershark.core-async-storage-test])) 4 | 5 | (doo-tests 'glittershark.core-async-storage-test) 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "core-async-storage", 3 | "version": "1.0.0", 4 | "description": "A Clojure wrapper around react-native's AsyncStorage using core.async", 5 | "dependencies": {}, 6 | "devDependencies": { 7 | "karma": "^0.13.22", 8 | "karma-chrome-launcher": "^0.2.3", 9 | "karma-cljs-test": "^0.1.0" 10 | }, 11 | "scripts": { "test": "lein test" }, 12 | "author": "Griffin Smith", 13 | "license": "MIT" 14 | } 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Griffin Smith 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject core-async-storage "0.3.1" 2 | :description "Clojurescript wrapper for react-native's AsyncStorage using 3 | core.async" 4 | :url "https://github.com/glittershark/core-async-storage" 5 | :license {:name "MIT License" 6 | :url "https://opensource.org/licenses/MIT"} 7 | :dependencies [[org.clojure/clojure "1.8.0"] 8 | [org.clojure/clojurescript "1.8.40"] 9 | [org.clojure/core.async "0.3.443"] 10 | [org.clojure/tools.reader "1.0.5"]] 11 | :plugins [[lein-cljsbuild "1.1.3"] 12 | [lein-doo "0.1.6"] 13 | [lein-codox "0.9.4"]] 14 | :codox {:language :clojurescript} 15 | :resource-paths ["resources" "target/resources"] 16 | :cljsbuild {:builds 17 | {:test 18 | {:source-paths ["src" "test"] 19 | :compiler {:output-to "target/resources/test.js" 20 | :output-dir "target/test/" 21 | :main glittershark.core-async-storage.test-runner 22 | :optimizations :none 23 | :pretty-print true 24 | :source-map false}}}} 25 | :doo {:build "test"}) 26 | -------------------------------------------------------------------------------- /test/glittershark/core_async_storage_test.clj: -------------------------------------------------------------------------------- 1 | (ns glittershark.core-async-storage-test 2 | (:require [clojure.test :refer :all] 3 | [clojure.core.async :refer [promise-chan go #'test-meta-fn meta :arglists)) 33 | "Preserves the :arglists meta of the name var")) 34 | -------------------------------------------------------------------------------- /src/glittershark/core_async_storage.clj: -------------------------------------------------------------------------------- 1 | (ns glittershark.core-async-storage) 2 | 3 | (defmacro ^:no-doc defcbfn 4 | "Define `fname' as a wrapper to a function `wrapped-fn' which takes a callback 5 | as its last argument that, instead of taking a callback, returns the result 6 | wrapped inside a core.async channel 7 | 8 | Supported options: 9 | 10 | :transducer - transducer to be applied to all return values in the 11 | channel 12 | :transform-args - function to apply to a sequence of the function arguments 13 | (excluding the callback) before supplying to the wrapped 14 | function" 15 | 16 | [fname wrapped-fn & {:keys [transducer transform-args] 17 | :or {transducer nil 18 | transform-args 'identity}}] 19 | 20 | ;; Evaluate in case the function expressions have side effects 21 | `(let [transducer# ~transducer 22 | transform-args# ~transform-args] 23 | (def ~fname 24 | (fn [& args#] 25 | (let 26 | [result-chan# (~'promise-chan transducer#) 27 | callback# (fn [& result#] 28 | (~'put! result-chan# (vec result#))) 29 | wrap-args# (-> args# 30 | transform-args# 31 | vec 32 | (conj callback#))] 33 | (apply ~wrapped-fn wrap-args#) 34 | result-chan#))))) 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Core.Async Storage [![Build Status](https://travis-ci.org/glittershark/core-async-storage.svg?branch=master)](https://travis-ci.org/glittershark/core-async-storage) 2 | 3 | A Clojurescript wrapper around react-native's [AsyncStorage][] using 4 | [core.async][] 5 | 6 | [AsyncStorage]: https://facebook.github.io/react-native/docs/asyncstorage.html#content 7 | [core.async]: https://github.com/clojure/core.async 8 | 9 | ## Installation 10 | 11 | [![Clojars Project](https://img.shields.io/clojars/v/core-async-storage.svg)](https://clojars.org/core-async-storage) 12 | 13 | ## Usage 14 | 15 | In general, all exposed functions are `kebab-case` versions of AsyncStorage's 16 | `camelCase` functions, that return a `core.async` channel instead of taking a 17 | callback as their last argument, and also convert all input values to EDN and 18 | read all return values as Clojure data structures 19 | 20 | ```clojure 21 | (ns my-ns.core 22 | (:require [glittershark.core-async-storage :refer [get-item set-item]] 23 | [cljs.core.async :refer [ [nil], or [error] 28 | (println ( [nil {:bar "baz"}], or [error nil] 29 | 30 | (let [a-map {:a "foo" :b "bar"} 31 | a-vec [[:a "foo"][:b "bar"]]] 32 | (go 33 | ;; multi-set can accept a map or a collection of vectors 34 | (println ( [nil {:bar "baz"}] 45 | (println ( [nil {:bar "baz"}] 46 | ``` 47 | 48 | [promise-chan]: https://clojure.github.io/core.async/#clojure.core.async/promise-chan 49 | 50 | ## TODO 51 | 52 | - [x] AsyncStorage.getItem 53 | - [x] AsyncStorage.setItem 54 | - [x] AsyncStorage.removeItem 55 | - [ ] AsyncStorage.mergeItem 56 | - [x] AsyncStorage.clear 57 | - [ ] AsyncStorage.getAllKeys 58 | - [ ] AsyncStorage.flushGetRequests 59 | - [x] AsyncStorage.multiGet 60 | - [x] AsyncStorage.multiSet 61 | - [ ] AsyncStorage.multiRemove 62 | - [ ] AsyncStorage.multiMerge 63 | 64 | ## License 65 | 66 | Copyright © 2017 Griffin Smith 67 | 68 | Distributed under the MIT License. 69 | -------------------------------------------------------------------------------- /src/glittershark/core_async_storage.cljs: -------------------------------------------------------------------------------- 1 | (ns glittershark.core-async-storage 2 | "Clojurescript wrapper around react-native's AsyncStorage using core.async 3 | 4 | In general, all functions in this namespace are kebab-case versions of 5 | AsyncStorage's camelCase functions, that return a `core.async' channel rather 6 | than taking a callback 7 | 8 | All keys and values passed to functions in this namespace will also be 9 | serialized to and from EDN before being stored" 10 | {:author "Griffin Smith"} 11 | 12 | (:require [cljs.core.async :refer [promise-chan array [c] 30 | (->> (map #(apply array %) c) 31 | (apply array) 32 | (array))) 33 | 34 | (defn- ?read-string [v] (if v (reader/read-string v) v)) 35 | 36 | (defn- method [mname] (-> async-storage 37 | (goog.object/get mname) 38 | (.bind async-storage))) 39 | 40 | (defcbfn 41 | ^{:doc "Fetches `key' and returns [error result] in a core.async channel, or 42 | [nil result] if no error" 43 | :arglists '([key]) 44 | :added "1.0.0"} 45 | get-item (method "getItem") 46 | :transducer (map (map-last ?read-string)) 47 | :transform-args (map-first pr-str)) 48 | 49 | (defcbfn 50 | ^{:doc "Fetches all `keys` and returns [errors? results] in a core.async 51 | channel, where `results` is a map from requested keys to their values 52 | in storage" 53 | :arglists '([keys]) 54 | :added "1.1.0"} 55 | multi-get (method "multiGet") 56 | :transducer (map (map-last 57 | #(->> % (map (partial mapv ?read-string)) (into {})))) 58 | :transform-args (map-first #(->> % (map pr-str) (apply array)))) 59 | 60 | (defcbfn 61 | ^{:doc "Sets `value' for `key' and returns [error] in a core.async channel 62 | upon completion, or [] if no error" 63 | :arglists '([key value]) 64 | :added "1.0.0"} 65 | set-item (method "setItem") 66 | :transform-args #(map pr-str %)) 67 | 68 | (defcbfn 69 | ^{:doc "Sets a `value' for each `key' in a collection and returns [error] in 70 | a core.async channel upon completion, or [] if no error" 71 | :arglists '([[key value]]) 72 | :added "1.2.0"} 73 | multi-set (method "multiSet") 74 | :transform-args (fn [c] (args->array (map #(map pr-str %) (first c))))) 75 | 76 | (defcbfn 77 | ^{:doc "Removes `key' from the storage and returns [error] in a core.async 78 | channel, or [] if no error" 79 | :arglists '([key value]) 80 | :added "1.0.0"} 81 | remove-item (method "removeItem") 82 | :transform-args (map-first pr-str)) 83 | 84 | (defcbfn 85 | ^{:doc "Removes each `key' in a collection from the storage and returns 86 | [error] in a core.async channel, or [] if no error" 87 | :arglists '([keys]) 88 | :added "1.2.0"} 89 | multi-remove (method "multiRemove") 90 | :transform-args (map-first #(->> % (map pr-str) (apply array)))) 91 | 92 | (defcbfn 93 | ^{:doc "Erases *all* AsyncStorage for all clients, libraries, etc. You 94 | probably don't want to call this - use removeItem or multiRemove to 95 | clear only your own keys instead. 96 | Returns [error] in a core.async channel, or [] if no error" 97 | :arglists '([key value]) 98 | :added "1.0.0"} 99 | clear (method "clear")) 100 | -------------------------------------------------------------------------------- /test/glittershark/core_async_storage_test.cljs: -------------------------------------------------------------------------------- 1 | (ns glittershark.core-async-storage-test 2 | (:require [cljs.test :refer-macros [deftest is testing async]] 3 | [cljs.core.async :refer [ args butlast vec)) 17 | (mock-fn (last args)))] 18 | (aset async-storage fname mock-fn*) 19 | call-args)) 20 | 21 | (deftest get-item-test 22 | (async done 23 | (go 24 | (testing "when the key exists in storage" 25 | (let [args (mock-storage-fn "getItem" #(% nil ":foobar"))] 26 | (is (= [nil :foobar] (clj @args)) 56 | "Converts all of the passed keys to EDN before passing them to 57 | AsyncStorage.multiGet") 58 | 59 | (is (js/Array.isArray (-> @args first first)) 60 | "Converts the list of keys to a JS array"))) 61 | 62 | (done)))) 63 | 64 | (deftest set-item-test 65 | (async done 66 | (go 67 | (let [args (mock-storage-fn "setItem" #(% nil))] 68 | (is (= [nil] (clj @args)) 85 | "converts the passed key and value to EDN before passing it to 86 | AsyncStorage.multiSet") 87 | 88 | (done))))) 89 | 90 | (deftest remove-item-test 91 | (async done 92 | (go 93 | (let [args (mock-storage-fn "removeItem" #(% nil))] 94 | (is (= [nil] (clj @args)) 111 | "converts the passed key to EDN before passing it to 112 | AsyncStorage.multiRemove") 113 | 114 | (done))))) 115 | 116 | (deftest clear-test 117 | (async done 118 | (go 119 | (let [args (mock-storage-fn "clear" #(% nil))] 120 | (is (= [nil] (