diff --git a/SPEC.md b/SPEC.md deleted file mode 100644 index dd34f70f..00000000 --- a/SPEC.md +++ /dev/null @@ -1,156 +0,0 @@ -# Moneta Specification - -(See RFC 2119 for use of MUST, SHOULD, MAY, MUST NOT, and SHOULD NOT) - -The purpose of the moneta specification is to create a general-purpose API for interacting with key-value stores. In general, libraries that need to interact with key-value stores should be able to specify that they can use any "moneta-compliant store". Moneta ships with a set of executable specs which you can use to verify spec-compliance with your moneta adapter. - -## Class Methods - -### new(options[Hash] => {}) => Object - -Return an instance of the moneta adapter, with the instance methods listed below. The options hash is a required parameter, and the adapter may specify whatever additional requirements it needs to properly instantiate it. - -## Instance Methods - -### \[\](key[Object]) => Object - -Return the value stored in the key-value-store under the provided key. Adapters MUST return a duplicate of the original value, and consumers should expect that adapters might serialize and deserialize the key and value. As a result, both the key and value MUST be objects that can be serialized using Ruby's Marshal system. - -### \[\]=(key[Object], value[Object]) => Object(value) - -Store the value in the key-value-store under the provided key. Adapters MAY serialize the value using Ruby's Marshal system, and MUST NOT store a reference to the original value in the store, unless Ruby disallows duplication of the original value. Adapters SHOULD NOT simply call dup on the value, unless the value stores no references to other Object. For example, an adapter MAY store a dup of a String, but SHOULD NOT store a dup of ["hello", "world"]. - -### fetch(key[Object], options[Hash] => {}, &block) => Object - -Return the value stored in the key-value-store under the provided key. If no value is stored under the provided key, the adapter MUST yield to the block, and return the value. The adapter MUST NOT store the value returned from the block in the key-value-store. - -### fetch(key[Object], value[Object], options[Hash] => {}) => Object - -Return the value stored in the key-value-store under the provided key. If no value is stored under the provided key, the adapter MUST return the default value provided. The adapter MUST NOT store the default value in the key-value-store. - -### delete(key[Object], options[Hash] => {}) => Object - -Delete the value stored in the key-value-store for the key provided, and return the value previously stored there. After this operation, the key-value-store MUST behave as though no value was stored for the provided key. - -### key?(key[Object], options[Hash] => {}) => [TrueClass, FalseClass] - -Determine whether a value exists in the key-value-store for the key provided. If a value exists, the adapter MUST return true. Otherwise, the adapter MUST return false. - -### store(key[Object], value[Object], options[Hash] => {}) => Object(value) - -Behaves the same as []=, but allows the client to send additional options which can be specified by the adapter (and which may be specified by extensions to this specification). - -### increment(key[Object], amount[Integer] = 1, options[Hash] => {}) => Integer(value) - -Increments a value atomically. This method is not supported by all stores and might raise a NotImplementedError. -This method MUST accept negative amounts, but the result MUST be unsigned. - -### decrement(key[Object], amount[Integer] = 1, options[Hash] => {}) => Integer(value) - -Decrements a value atomically. This method is not supported by all stores and might raise a NotImplementedError. -This method MUST accept negative amounts, but the result MUST be unsigned. - -### create(key[Object], value[Object], options[Hash] => {}) => [TrueClass, FalseClass] - -Creates a value atomically. This method is not supported by all stores and might raise a NotImplementedError. -It MUST return true if the value was created. - -### clear(options[Hash] => {}) - -Completely empty all keys and values from the key-value-store. Adapters MAY allow a namespace during initialization, which can scope this operation to a particular subset of keys. After calling clear, a [] operation MUST return nil for every possible key, and a key? query MUST return false for every possible key. - -### close - -Closes the store - -### features => Array<Symbol> and supports?(Symbol) => [TrueClass, FalseClass] - -Feature detection. Adapters MUST return :create and :increment if these methods are supported. - -### `each_key => Enumerator` and `each_key(&block) => Object` - -Enumerates over the keys in the store. This method is not supported by all -stores. When not supported, this method MUST raise a `NotImplementedError`, -regardless of whether a block is supplied. When supported, this method allows -traversal of all keys in the store. The method behaves differently depending on -whether a block is supplied. In either case, for each key, `k` in the -traversal, `key?(k)` MUST return `true`; and for each key, `k` for which -`key?(k)` returns `true`, `k` MUST be traversed by `each_key`. Keys MAY be -traversed in any order. Mutation of the store while traversing keys MAY be -allowed. Querying the store (calling `fetch`, `key?`, etc.) while traversing -MUST be allowed. - -* If no block is supplied, `each_key` MUST return an `Enumerator` that can be - used to traverse each key (e.g. by calling `each`). Calling methods on the - `Enumerator` such as `each` with a block MUST return the store object. - -* If a block is supplied, that block MUST be called once with each traversed key - as the only argument. When called in this way, `each_key` MUST return the - store object. - -### `values_at(*keys[Array], **options[Hash]) => Array` - -Returns an array containing the values associated with the given keys, in the -same order as the supplied keys. If a key is not present in the -key-value-store, `nil` MUST be returned in its place. For each key, and each -value, the same restrictions apply as apply to individual keys passed to, and -values received from the store in the specification of `[]` (see above). The -adapter MAY perform this operation atomically. - -### `fetch_values(*keys[Array], **options[Hash], &defaults) => Array` - -Behaves identically to `values_at`, except that it MUST accept an optional -block. When supplied, the block will be called successively with each supplied -key that is not present in the store. The return value of the block call MUST -be used in place of `nil` in returned the array of values. As with `fetch` -(above), the adapter MUST NOT store the return value of the block call in the -key-value-store. The adapter MAY perform this operation atomically. - -### `slice(*keys[Array], **options[Hash]) => ` - -Returns a collection of key-value pairs corresponding to those supplied keys -which are present in the key-value store, and their associated values. A key -MUST be present in the return value if and only if it was supplied in the `keys` -parameter and it is present in the key-value store. For each key, and each -value, the same restrictions apply as apply to individual keys passed to, and -values received from the store in the specification of `[]` (see above). The -adapter MAY perform this operation atomically. - -### `merge!(pairs[], options[Hash] => {}, &block) => self` - -Stores the pairs in the key-value-store, and returns the store object. This -method MUST behave identically to successively calling `[]=` with each key-value -pair and the options hash; except that the adapter MAY perform this operation -atomically, and the method MUST accept an optional block, which MUST be called -for each key that is to be overwritten. When the block is provided, it MUST be -called before overwriting any existing values with the key, old value and -supplied value, and the return value of the block MUST be used in place of the -supplied value. `merge!` MUST also be aliased as `update`. - - -## Additional Options Hashes - -The following methods may all take an additional Hash as a final argument. This allows the client to send additional options which can be specified by the adapter (and which may be specified by extensions to this specification). The methods MUST NOT modify the supplied option hash. - -* fetch -* load -* store -* delete -* key? -* increment -* clear -* merge! - -Additionally, the following methods accept options as keyword arguments, after -non-keyword arguments. These keyword arguments are treated as a hash, -equivalent to supplying a hash to the above methods. - -* values_at -* fetch_values -* slice - -In the case of methods with optional arguments, the Hash MUST be provided as the final argument. Keys in this Hash MUST be Symbols. - -## Atomicity - -The base Moneta specification does not specify any atomicity guarantees. However, extensions to this spec may specify extensions that define additional guarantees for any of the defined operations. diff --git a/SPEC.rbs b/SPEC.rbs new file mode 100644 index 00000000..89e885a6 --- /dev/null +++ b/SPEC.rbs @@ -0,0 +1,209 @@ +# Moneta Specification +# +# (See RFC 2119 for use of MUST, SHOULD, MAY, MUST NOT, and SHOULD NOT) +# +# The purpose of the moneta specification is to create a general-purpose API for interacting with +# key-value stores. In general, libraries that need to interact with key-value stores should be able +# to specify that they can use any "moneta-compliant store". Moneta ships with a set of executable +# specs which you can use to verify spec-compliance with your moneta adapter. +# +# The specification is written as an RBS file. It is based on the RBS for the Hash class: +# * https://github.com/ruby/rbs/blob/master/core/hash.rbs +# +# ### Additional Options +# +# The following methods all accept options as keyword arguments, after non-keyword arguments. This +# allows the client to send additional options which can be specified by the adapter (and which may +# be specified by extensions to this specification). +# +# * fetch +# * load +# * store +# * delete +# * key? +# * increment +# * clear +# * merge! +# * values_at +# * fetch_values +# * slice +# +# ### Atomicity +# +# The base Moneta specification does not specify any atomicity guarantees. However, extensions to +# this spec may specify extensions that define additional guarantees for any of the defined +# operations. +# +interface _Store[unchecked out K, unchecked out V] + # Initialize an instance of the moneta adapter, with the instance methods listed below. + # + # Options MAY be supplied as keyword arguments. Adapters MAY require certain options be provided + # in order to properly instantiate + # + def initialize: (**untyped options) -> void + + # Return the value stored in the key-value-store under the provided key. + # + # Adapters MUST return a duplicate of the original value, and consumers should expect that + # adapters might serialize and deserialize the key and value. As a result, both the key and value + # MUST be objects that can be serialized using Ruby's Marshal system. + # + def []: (K key) -> V + + # Store the value in the key-value-store under the provided key. + # + # Adapters MAY serialize the value using Ruby's Marshal system, and MUST NOT store a reference to + # the original value in the store, unless Ruby disallows duplication of the original value. + # Adapters SHOULD NOT simply call `dup` on the value, unless the value stores no references to + # other Object. For example, an adapter MAY store a `dup` of a String, but SHOULD NOT store a + # `dup` of `["hello", "world"]`. + # + def []=: (K key, V value) -> V + + # Return the value stored in the key-value-store under the provided key. + # + # In the first form, if no value is stored under the provided key, the adapter MUST yield to the + # block, and return the value. + # + # In the second form, if no value is stored under the provided key, the adapter MUST return the + # default value provided. + # + # The adapter MUST NOT store the value returned from the block in the key-value-store. + # + def fetch: [X] (K key, **untyped options) { (K key) -> X } -> (V | X) + | [X] (K key, X default_value, **untyped options) -> (V | X) + + # Delete the value stored in the key-value-store for the key provided, and return the value + # previously stored there, or `nil` if nothing was stored at that key. + # + # After this operation, the key-value-store MUST behave as though no value was stored for the + # provided key. + # + def delete: (K key, **untyped options) -> V? + + # Determine whether a value exists in the key-value-store for the key provided. If a value exists, + # the adapter MUST return `true`. Otherwise, the adapter MUST return `false`. + def key?: (K key, **untyped options) -> bool + + # Behaves the same as _Store#[]=, but allows the client to send additional options which can be + # specified by the adapter (and which may be specified by extensions to this specification). + def store: (K key, V value, **untyped options) -> V + + # Increments a value atomically. + # + # This method is not supported by all stores and might raise a `NotImplementedError`. + # + # An amount to increment by MAY be provided as the second argument. This method MUST accept + # negative amounts, but the result MUST be a whole number. + def increment: (K key, **untyped options) -> Integer + | (K key, Integer amount, **untyped options) -> Integer + + # Decrements a value atomically. + # + # This method is not supported by all stores and might raise a `NotImplementedError`. + # + # This method MUST accept negative amounts, but the result MUST be a whole number. + # + def decrement: (K key, **untyped options) -> Integer + | (K key, Integer amount, **untyped options) -> Integer + + + # Creates a value atomically. + # + # This method is not supported by all stores and might raise a `NotImplementedError`. + # + # It MUST return `true` if the value was created, and `false` otherwise. + # + def create: (K key, V value, **untyped options) -> bool + + # Completely empty all keys and values from the key-value-store. + # + # Adapters MAY allow a namespace during initialization, which can scope this operation to a + # particular subset of keys. + # + # After calling `clear`, a `[]` operation MUST return nil for every possible key, and a `key?` + # query MUST return `false` for every possible key. + # + def clear: (**untyped options) -> void + + # Closes the store + # + def close: () -> void + + # Feature detection. + # + # Adapters MUST return `:create` and `:increment` if these methods are supported. + # + def features: () -> Array[Symbol] + def supports?: (Symbol) -> bool + + # Enumerates over the keys in the store. + # + # This method is not supported by all stores. When not supported, this method MUST raise a + # `NotImplementedError`, regardless of whether a block is supplied. + # + # When supported, this method allows traversal of all keys in the store. The method behaves + # differently depending on whether a block is supplied. In either case, for each key, `k` in + # the traversal, `key?(k)` MUST return `true`; and for each key, `k` for which `key?(k)` returns + # `true`, `k` MUST be traversed by `each_key`. Keys MAY be traversed in any order. Mutation of + # the store while traversing keys MAY be allowed. Querying the store (calling `fetch`, `key?`, + # etc.) while traversing MUST be allowed. + # + # * If no block is supplied, `each_key` MUST return an `Enumerator` that can be used to traverse + # each key (e.g. by calling `each`). Calling methods on the `Enumerator` such as `each` with a + # block MUST return the store object. + # + # * If a block is supplied, that block MUST be called once with each traversed key as the only + # argument. When called in this way, `each_key` MUST return the store object. + # + def each_key: () { (K key) -> untyped } -> self + | () -> Enumerator[K, self] + + # Returns an array containing the values associated with the given keys, in the same order as the + # supplied keys. + # + # If a key is not present in the key-value-store, `nil` MUST be returned in its place. For each + # key, and each value, the same restrictions apply as apply to individual keys passed to, and + # values received from the store in the specification of `[]` (see above). The adapter MAY + # perform this operation atomically. + # + def values_at: (*K keys, **untyped options) -> Array[V] + + # Behaves identically to `values_at`, except that it MUST accept an optional block. When + # supplied, the block will be called successively with each supplied key that is not present in + # the store. + # + # The return value of the block call MUST be used in place of `nil` in returned the array of + # values. As with `fetch` (above), the adapter MUST NOT store the return value of the block + # call in the key-value-store. The adapter MAY perform this operation atomically. + # + def fetch_values: (*K keys, **untyped options) -> Array[V] + | [X] (*K keys, **untyped options) { (K) -> X } -> Array[V | X] + + # Returns a collection of key-value pairs corresponding to those supplied keys which are present + # in the key-value store, and their associated values. + # + # A key MUST be present in the return value if and only if it was supplied in the `keys` parameter + # and it is present in the key-value store. For each key, and each value, the same restrictions + # apply as apply to individual keys passed to, and values received from the store in the + # specification of `[]` (see above). The adapter MAY perform this operation atomically. + # + def slice: (*K keys, **untyped options) -> Enumerable[[K, V]] + + # Stores the pairs in the key-value-store, and returns the store object. + # + # This method MUST behave identically to successively calling `[]=` with each key-value pair and + # the options hash; except that the adapter MAY perform this operation atomically, and the method + # MUST accept an optional block, which MUST be called for each key that is to be overwritten. + # + # When the block is provided, it MUST be called before overwriting any existing values with the + # key, old value and supplied value, and the return value of the block MUST be used in place of + # the supplied value. `merge!` MUST also be aliased as `update`. + # + def merge!: (Enumerable[[K, V]] pairs, **untyped options) -> self + | (Enumerabke[[K, V]] pairs, **untyped options) { (K key, V oldval, V newval) -> V } -> self + + # See #merge + # + alias update merge! +end