diff --git a/spec.bs b/spec.bs index ad96719..d9e2af5 100644 --- a/spec.bs +++ b/spec.bs @@ -192,6 +192,25 @@ This specification proposes a new API, called KV storage, which is intended to p
std:kv-storage
built-in module+[SecureContext] +module kv-storage { + [Constructor(DOMString name), SameRealmBrandCheck, UnenumerableMethods] + interface StorageArea { + Promise<void> set(any key, any value); + Promise<any> get(any key); + Promise<void> delete(any key); + Promise<void> clear(); + ++[=StorageArea/get the next iteration result|async_iterable<any, any>=] ; + + [SameObject] readaonly attribute object backingStore; + }; + + readonly attribute kv-storage.StorageArea storage; +}; +
import * as |kvs| from "std:kv-storage"
std:kv-storage
" to denote it for now. This is not final and is subject to change depending on the details of how built-in modules end up working. [[JSSTDLIB]]
+This specification defines a new built-in module. Tentatively, depending on further discussions, we use the specifier "std:kv-storage
" to denote it for now. This is not final and is subject to change depending on the details of how built-in modules end up working. [[JSSTDLIB]]
Its exports are the following:
: storage
-:: An instance of the {{StorageArea}} class, created as if by [$Construct$]({{StorageArea}}, « "default
" »).
+:: An instance of the {{StorageArea}} class, backed by a database named "kv-storage:default
".
: StorageArea
:: The {{StorageArea}} class
@@ -228,61 +247,9 @@ Its exports are the following:
-In addition to establishing its exports, evaluating the module must perform the following steps:
-
-1. If the [=current settings object=] is not [$Is an environment settings object contextually secure?|contextually secure$], throw a "{{SecurityError}}" {{DOMException}}.
-
StorageArea
classstd:kv-storage
module, the {{StorageArea}} class must be created in the [=current realm=]. The result must be equivalent to evaluating the following JavaScript code, with the following two exceptions:
-
-The constructor, method, and getter bodies must behave as specified below instead of being the no-ops shown in this code block.
[$HostHasSourceTextAvailable$] must return false for all function objects (i.e. the constructor, methods, and getter) created.
- class StorageArea { - constructor(name) { /* see below */ } - - set(key, value) { /* see below */ } - get(key) { /* see below */ } - delete(key) { /* see below */ } - clear() { /* see below */ } - - keys() { /* see below */ } - values() { /* see below */ } - entries() { /* see below */ } - - get backingStore() { /* see below */ } - } -- -The
prototype
property of {{StorageArea}} must additionally have a [=@@asyncIterator=] property, whose value is equal to the same function object as the original value of StorageArea.prototype.entries()
.
-
-The intention of defining the {{StorageArea}} class in this way, using a skeleton JavaScript class definition, is to automatically establish the various properties of the class, its methods, and its getter, which otherwise need to be specified in tedious detail. For example, this automatically establishes the length
and name
properties of all these functions, their property descriptors, their prototype
and constructor
properties, etc. And it does so in a way that is consistent with what a JavaScript developer would expect.
-
- Why not use Web IDL?
-
- Apart from the above novel technique, there are two commonly-seen alternatives for defining JavaScript classes. The JavaScript specification, as well as the Streams Standard, defer to the "ECMAScript Standard Built-in Objects" section of the JavaScript specification, which defines many defaults. The more popular alternative, however, is to use Web IDL. Why aren't we using that?
-
- Web IDL has a few minor mismatches with our goals for built-in modules:
-
- * Its automatically-generated brand checks are both unforgeable and cross-realm, which is not accomplishable in JavaScript. [=StorageArea/brand check|Our brand checks=] are same-realm-only, as we would like built-in modules to not have special privileges in this regard over non-built-in ones.
-
- * It does not have a mechanism for exposing classes inside modules; instead they are always exposed on some set of global objects.
-
- * It produces methods and accessors that are enumerable, which does not match the natural JavaScript implementation. This would make it more difficult to implement a Web IDL-specified built-in module in JavaScript. (But not impossible.)
-
- * The generic nature of Web IDL means that it is best implemented using code generation. However, most implementers currently do not have a Web IDL bindings generator that wraps JavaScript; using Web IDL would virtually require them to either implement the built-in modules in C++, or create such a bindings generator. Furthermore, the wrappers end up being quite large; see an example.
-
- None of these mismatches are fatal. We could switch this specification to Web IDL, with appropriate extensions for solving the first two problems, if that ends up being desired. We recognize that the goals for built-in modules are still under active discussion, and the above might not end up being important in the end. But for now, we're experimenting with this different—and more aligned-with-JavaScript-modules—mechanism of specifying a class definition.
-
The realm check here gives us semantics identical to using JavaScript's {{WeakMap}} or the proposed private class fields. This ensures {{StorageArea}} does not use any magic, like the platform's usual cross-realm brand checks, which go beyond what can be implemented in JavaScript. [[ECMA-262]] [[CLASS-FIELDS]] +
The [SameRealmBrandCheck] [=extended attribute=] here gives us semantics identical to using JavaScript's {{WeakMap}} or the proposed private class fields. This ensures {{StorageArea}} does not use any magic, like the platform's usual cross-realm brand checks, which go beyond what can be implemented in JavaScript. [[ECMA-262]] [[CLASS-FIELDS]]
This does not actually open or create the database yet; that is done lazily when other methods are called. This means that all other methods can reject with database-related exceptions in failure cases. -
kv-storage:
" and |nameString|.
- 1. Set |area|.[=[[DatabasePromise]]=] to null.
- 1. Set |area|.[=[[BackingStoreObject]]=] to null.
+StorageArea(|name|)
constructor, when
+invoked, must run these steps:
+
+ 1. Set this.[=[[DatabaseName]]=] to the concatenation of "kv-storage:
" and |name|.
+ 1. Set this.[=[[DatabasePromise]]=] to null.
+ 1. Set this.[=[[BackingStoreObject]]=] to null.
readonly
", and the following steps operating on |transaction| and |store|:
+ 1. Let |lastKey| be current state.
+ 1. Let |range| be the result of [=getting the range for=] |lastKey|.
+ 1. Let |keyRequest| be the result of performing the steps listed in the description of {{IDBObjectStore}}'s {{IDBObjectStore/getKey()}} method on |store|, given the argument |range|.
+ 1. Let |valueRequest| be the result of performing the steps listed in the description of {{IDBObjectStore}}'s {{IDBObjectStore/get()}} method on |store|, given the argument |range|.
+ 1. Let |promise| be [=a new promise=].
+ 1. [=Add a simple event listener=] to |valueRequest| for "success
" that performs the following steps:
+ 1. Let |key| be |keyRequest|'s [=request/result=].
+ 1. If |key| is undefined, then:
+ 1. [=Resolve=] |promise| with undefined.
+ 1. Otherwise:
+ 1. Let |value| be |valueRequest|'s [=request/result=].
+ 1. [=Resolve=] |promise| with (|key|, |value|, |key|).
+ 1. [=Add a simple event listener=] to |keyRequest| for "error
" that [=rejects=] |promise| with |keyRequest|'s [=request/error=].
+ 1. [=Add a simple event listener=] to |valueRequest| for "error
" that [=rejects=] |promise| with |valueRequest|'s [=request/error=].
+ 1. Return |promise|.
+
+
+for await (const |key| of |storage|.{{StorageArea/keys()|keys}}()) { ... }
@@ -465,12 +456,6 @@ To delete the database
The iterator provides a live view onto the storage area: modifications made to entries sorted after the last-returned one will be reflected in the iteration.
keys
".
-for await (const |value| of |storage|.{{StorageArea/values()|values}}()) { ... }
@@ -507,13 +492,7 @@ To delete the database
The iterator provides a live view onto the storage area: modifications made to entries sorted after the last-returned one will be reflected in the iteration.
values
".
-for await (const [|key|, |value|] of |storage|.{{StorageArea/entries()|entries}}()) { ... }
@@ -526,12 +505,6 @@ To delete the database
The iterator provides a live view onto the storage area: modifications made to entries sorted after the last-returned one will be reflected in the iteration.
entries
".
-[=storage=]
, you could use the following code to send all locally-stored entries to a server:
@@ -566,16 +539,14 @@ To delete the database
database
", |area|.[=[[DatabaseName]]=]).
+ 1. Perform [$CreateDataProperty$](|backingStoreObject|, "database
", this.[=[[DatabaseName]]=]).
1. Perform [$CreateDataProperty$](|backingStoreObject|, "store
", "store
").
1. Perform [$CreateDataProperty$](|backingStoreObject|, "version
", 1).
1. Perform [$SetIntegrityLevel$](|backingStoreObject|, "frozen
").
- 1. Set |area|.[=[[BackingStoreObject]]=] to |backingStoreObject|.
- 1. Return |area|.[=[[BackingStoreObject]]=].
+ 1. Set this.[=[[BackingStoreObject]]=] to |backingStoreObject|.
+ 1. Return this.[=[[BackingStoreObject]]=].
Much of this section is amenable to being generalized into a reusable primitive, probably via Web IDL. See heycam/webidl#580.
- -Upon evaluating thestd:kv-storage
module, let the storage area async iterator prototype object be object obtained via the following steps executed in the [=current realm=]:
-
-1. Let |proto| be [$ObjectCreate$]({{%AsyncIteratorPrototype%}}).
-1. Let |next| be [$CreateBuiltinFunction$](the steps of [[#storageareaasynciterator-next]]).
-1. Perform [$CreateMethodProperty$](|proto|, "next
", |next|).
-1. Return |proto|.
-
-keys
", "values
", or "entries
":
-
-1. Let |iter| be [$ObjectCreate$]([=the storage area async iterator prototype object=], « \[[Area]], \[[Mode]], \[[LastKey]], \[[OngoingPromise]] »).
-1. Set |iter|.[=[[Area]]=] to |area|.
-1. Set |iter|.[=[[Mode]]=] to |mode|.
-1. Set |iter|.[=[[LastKey]]=] to [=not yet started=].
-1. Set |iter|.[=[[OngoingPromise]]=] to undefined.
-1. Return |iter|.
-
-The following is a non-normative summary of the internal slots that get added to objects created in such a way:
-
-keys
", "values
", or "entries
", indicating the types of values that iteration will retrieve from the storage area.
-
- read
", and the following steps operating on |transaction| and |store|:
- 1. Let |lastKey| be |iter|.[=[[LastKey]]=].
- 1. If |lastKey| is undefined, then return [$CreateIterResultObject$](undefined, true).
- 1. Let |range| be the result of [=getting the range for=] |lastKey|.
- 1. Let |key| and |iterResultValue| be null.
- 1. Let |promise| be [=a new promise=].
- 1. Switch on |iter|.[=[[Mode]]=]:
- keys
"success
" that performs the following steps:
- 1. Set |key| to |request|'s [=request/result=].
- 1. Set |iterResultValue| to |key|.
- 1. [=Finish up=].
- 1. [=Add a simple event listener=] to |request| for "error
" that [=rejects=] |promise| with |request|'s [=request/error=].
- values
"success
" that performs the following steps:
- 1. Set |key| to |keyRequest|'s [=request/result=].
- 1. Set |iterResultValue| to |valueRequest|'s [=request/result=].
- 1. [=Finish up=].
- 1. [=Add a simple event listener=] to |keyRequest| for "error
" that [=rejects=] |promise| with |keyRequest|'s [=request/error=].
- 1. [=Add a simple event listener=] to |valueRequest| for "error
" that [=rejects=] |promise| with |valueRequest|'s [=request/error=].
- entries
"success
" that performs the following steps:
- 1. Set |key| to |keyRequest|'s [=request/result=].
- 1. Let |value| be |valueRequest|'s [=request/result=].
- 1. Set |iterResultValue| to [$CreateArrayFromList$](« |key|, |value| »).
- 1. [=Finish up=].
- 1. [=Add a simple event listener=] to |keyRequest| for "error
" that [=rejects=] |promise| with |keyRequest|'s [=request/error=].
- 1. [=Add a simple event listener=] to |valueRequest| for "error
" that [=rejects=] |promise| with |valueRequest|'s [=request/error=].
- IDBFactory
is the {{IDBFactory}} instance re
To perform a database operation given a {{StorageArea}} |area|, a mode string |mode|, and a set of steps |steps| that operate on an {{IDBTransaction}} |transaction| and an {{IDBObjectStore}} |store|:
Function.prototype.toString()
for the functions produced. See drufball/layered-apis#7.
+* The output of Function.prototype.toString()
for the functions produced is censored. See drufball/layered-apis#7.
* By directly invoking the algorithms of various IDL operations and attributes, instead of going through the global, potentially-overridable JavaScript APIs. (E.g., in various algorithm steps that say "performing the steps listed in the description of", or the [=allowed as a key=] algorithm which uses [$IsArray$] directly instead of going through Array.isArray()
.) See drufball/layered-apis#6.