polystore 0.9.3 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/assets/home.html CHANGED
@@ -77,7 +77,8 @@ console.log(await store.get(key));
77
77
  <a href="/documentation#getting-started">Getting started</a>,
78
78
  <a href="/documentation#api">API</a>,
79
79
  <a href="/documentation#clients">Clients</a> and
80
- <a href="/documentation#examples">examples</a> for your convenience.
80
+ <a href="/documentation#creating-a-store">custom stores</a> for your
81
+ convenience.
81
82
  </p>
82
83
  </div>
83
84
  </div>
@@ -152,7 +153,7 @@ console.log(await store.get(key));
152
153
  <p>
153
154
  At
154
155
  <a href="https://bundlephobia.com/package/polystore" target="_blank"
155
- >just <strong>2.4kb</strong></a
156
+ >just <strong>3kb</strong></a
156
157
  >
157
158
  (min+gzip), the impact on your app loading time is minimal.
158
159
  </p>
@@ -180,6 +181,7 @@ console.log(await store.get(key));
180
181
  <h3>Intuitive expirations</h3>
181
182
  </header>
182
183
  <p>Write the expiration as <code>100s</code>, <code>1week</code>, etc.</p>
184
+ and forget time-related bugs.
183
185
  </div>
184
186
  </div>
185
187
  </section>
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "polystore",
3
- "version": "0.9.3",
3
+ "version": "0.10.0",
4
4
  "description": "A small compatibility layer for many popular KV stores like localStorage, Redis, FileSystem, etc.",
5
- "homepage": "https://github.com/franciscop/polystore",
5
+ "homepage": "https://polystore.dev/",
6
6
  "repository": "https://github.com/franciscop/polystore.git",
7
7
  "bugs": "https://github.com/franciscop/polystore/issues",
8
8
  "funding": "https://www.paypal.me/franciscopresencia/19",
@@ -25,10 +25,10 @@
25
25
  ],
26
26
  "license": "MIT",
27
27
  "devDependencies": {
28
- "check-dts": "^0.7.2",
28
+ "@deno/kv": "^0.8.1",
29
+ "check-dts": "^0.8.0",
29
30
  "dotenv": "^16.3.1",
30
31
  "edge-mock": "^0.0.15",
31
- "esbuild": "^0.19.4",
32
32
  "etcd3": "^1.1.2",
33
33
  "jest": "^29.7.0",
34
34
  "jest-environment-jsdom": "^29.7.0",
@@ -36,6 +36,17 @@
36
36
  "localforage": "^1.10.0",
37
37
  "redis": "^4.6.10"
38
38
  },
39
+ "documentation": {
40
+ "title": "🏬 Polystore - A universal library for standardizing any KV-store",
41
+ "home": "assets/home.html",
42
+ "homepage": "https://polystore.dev/",
43
+ "menu": {
44
+ "Documentation": "/documentation",
45
+ "Issues": "https://github.com/franciscop/polystore/issues",
46
+ "Get help": "https://superpeer.com/francisco/-/javascript-and-react-help",
47
+ "Github": "https://github.com/franciscop/polystore"
48
+ }
49
+ },
39
50
  "jest": {
40
51
  "testEnvironment": "jsdom",
41
52
  "setupFiles": [
package/readme.md CHANGED
@@ -36,13 +36,13 @@ Available clients for the KV store:
36
36
  - [**File** `new URL('file:///...')`](#file) (be): store the data in a single JSON file in your FS
37
37
  - [**Redis Client** `redisClient`](#redis-client) (be): use the Redis instance that you connect to
38
38
  - [**Cloudflare KV** `env.KV_NAMESPACE`](#cloudflare-kv) (be): use Cloudflare's KV store
39
- - [**Level** `new Level('example', { valueEncoding: 'json' })`](#level): support the whole Level ecosystem
40
- - [**Etcd** `new Etcd3()`](#etcd): the Microsoft's high performance KV store.
39
+ - [**Level** `new Level('example', { valueEncoding: 'json' })`](#level) (fe+be): support the whole Level ecosystem
40
+ - [**Etcd** `new Etcd3()`](#etcd) (be): the Microsoft's high performance KV store.
41
41
  - [**_Custom_** `{}`](#creating-a-store) (?): create your own store with just 3 methods!
42
42
 
43
43
  > This library should be as performant as the client you use with the item methods (GET/SET/ADD/HAS/DEL). For other and advanced cases, see [the performance considerations](#performance) and read the docs on your client.
44
44
 
45
- I made this library to be used as a "building block" of other libraries, so that _your library_ can accept many cache stores effortlessly! It's isomorphic (Node.js and the Browser) and tiny (~2KB). For example, let's say you create an API library, then you can accept the stores from your client:
45
+ I made this library to be used as a "building block" of other libraries, so that _your library_ can accept many cache stores effortlessly! It's isomorphic (Node.js, Bun and the Browser) and tiny (~2KB). For example, let's say you create an API library, then you can accept the stores from your client:
46
46
 
47
47
  ```js
48
48
  import MyApi from "my-api";
@@ -54,6 +54,38 @@ MyApi({ cache: env.KV_NAMESPACE }); // OR
54
54
  // ...
55
55
  ```
56
56
 
57
+ ## Getting started
58
+
59
+ First, install `polystore` and whatever [supported client](#clients) that you prefer. Let's see Redis as an example here:
60
+
61
+ ```
62
+ npm i polystore redis
63
+ ```
64
+
65
+ Then import both, initialize the Redis client and pass it to Polystore:
66
+
67
+ ```js
68
+ import kv from "polystore";
69
+ import { createClient } from "redis";
70
+
71
+ // Import the Redis configuration
72
+ const REDIS = process.env.REDIS_URL;
73
+
74
+ // Wrap the redis creation with Polystore (kv())
75
+ const store = kv(createClient(REDIS).connect());
76
+ ```
77
+
78
+ Now your store is ready to use! Add, set, get, del different keys. [See full API](#api).
79
+
80
+ ```js
81
+ const key = await store.add("Hello");
82
+
83
+ console.log(await store.get(key));
84
+ // Hello
85
+
86
+ await store.del(key);
87
+ ```
88
+
57
89
  ## API
58
90
 
59
91
  See how to initialize each store [in the Clients list documentation](#clients). But basically for every store, it's like this:
@@ -79,7 +111,7 @@ client.connect();
79
111
  const store = kv(client);
80
112
  ```
81
113
 
82
- While you can keep a reference to the store and access it directly, we strongly recommend if you are going to use a store, to only access it through `polystore`, since we do add custom serialization and extra properties for e.g. expiration time:
114
+ While you can keep a reference to the store and access it directly, we strongly recommend if you are going to use a store, to only access it through `polystore`, since we might add custom serialization and extra properties for e.g. expiration time:
83
115
 
84
116
  ```js
85
117
  const map = new Map();
@@ -515,19 +547,19 @@ Please see the [creating a store](#creating-a-store) section for more details!
515
547
 
516
548
  ## Performance
517
549
 
518
- > TL;DR: if you only use the item operations (add,set,get,has,del) and your store supports expiration natively, you have nothing to worry about!
550
+ > TL;DR: if you only use the item operations (add,set,get,has,del) and your client supports expiration natively, you have nothing to worry about!
519
551
 
520
552
  While all of our stores support `expires`, `.prefix()` and group operations, the nature of those makes them to have different performance characteristics.
521
553
 
522
- **Expires** we polyfill expiration when the underlying library does not support it. The impact on read/write operations and on data size of each key should be minimal. However, it can have a big impact in storage size, since the expired keys are not evicted automatically. Note that when attempting to read an expired key, polystore **will delete that key**. However, if an expired key is never read, it would remain in the datastore and could create some old-data issues. This is **especially important where sensitive data is involved**! To fix this, the easiest way is calling `await store.entries();` on a cron job and that should evict all of the old keys (this operation is O(n) though, so not suitable for calling it on EVERY API call, see the next point).
554
+ **Expires** we polyfill expiration when the underlying client library does not support it. The impact on read/write operations and on data size of each key should be minimal. However, it can have a big impact in storage size, since the expired keys are not evicted automatically. Note that when attempting to read an expired key, polystore **will delete that key**. However, if an expired key is never read, it would remain in the datastore and could create some old-data issues. This is **especially important where sensitive data is involved**! To fix this, the easiest way is calling `await store.entries();` on a cron job and that should evict all of the old keys (this operation is O(n) though, so not suitable for calling it on EVERY API call, see the next point).
523
555
 
524
- **Group operations** these are there mostly for small datasets only, for one-off scripts or for dev purposes, since by their own nature they can _never_ be high performance. But this is normal if you think about traditional DBs, reading a single record by its ID is O(1), while reading all of the IDs in the DB into an array is going to be O(n). Same applies with polystore.
556
+ **Group operations** these are there mostly for small datasets only, for one-off scripts or for dev purposes, since by their own nature they can _never_ be high performance in the general case. But this is normal if you think about traditional DBs, reading a single record by its ID is O(1), while reading all of the IDs in the DB into an array is going to be O(n). Same applies with polystore.
525
557
 
526
558
  **Substores** when dealing with a `.prefix()` substore, the same applies. Item operations should see no performance degradation from `.prefix()`, but group operations follow the above performance considerations. Some engines might have native prefix support, so performance in those is better for group operations in a substore than the whole store. But in general you should consider `.prefix()` as a convenient way of classifying your keys and not as a performance fix for group operations.
527
559
 
528
560
  ## Expires
529
561
 
530
- > Warning: if a client doesn't support expiration natively, we will hide expired keys on the API calls for a nice DX, but _old data might not be evicted automatically_, which is relevant especially for sensitive information. You'd want to set up a cron job to evict it manually, since for large datasets it might be more expensive (O(n)).
562
+ > Warning: if a client doesn't support expiration natively, we will hide expired keys on the API calls for a nice DX, but _old data might not be evicted automatically_. See [the notes in Performance](#performance) for details on how to work around this.
531
563
 
532
564
  We unify all of the clients diverse expiration methods into a single, easy one with `expires`:
533
565
 
@@ -572,7 +604,7 @@ For these and more situations, you can use `.prefix()` to simplify your life fur
572
604
 
573
605
  ## Creating a store
574
606
 
575
- To create a store, you define a class with these methods:
607
+ To create a store, you define a class with these properties and methods:
576
608
 
577
609
  ```js
578
610
  class MyClient {
@@ -580,7 +612,7 @@ class MyClient {
580
612
  // the `.set()` and `.add()` receive a `expires` that is a `null` or `number`:
581
613
  EXPIRES = false;
582
614
 
583
- // Mandatory methods (2 item-methods, 2 group-methods)
615
+ // Mandatory methods
584
616
  get (key): Promise<any>;
585
617
  set (key, value, { expires: null|number }): Promise<null>;
586
618
  entries (prefix): Promise<[string, any][]>;
@@ -600,7 +632,7 @@ class MyClient {
600
632
  }
601
633
  ```
602
634
 
603
- Note that this is NOT the public API, it's the internal **client** API. It's simpler than the public API since we do some of the heavy lifting as an intermediate layer (e.g. the `expires` will always be a `null` or `number`, never `undefined` or a `string`), but also it differs from polystore's API, like `.add()` has a different signature, and the group methods all take a explicit prefix.
635
+ Note that this is NOT the public API, it's the internal **client** API. It's simpler than the public API since we do some of the heavy lifting as an intermediate layer (e.g. for the client, the `expires` will always be a `null` or `number`, never `undefined` or a `string`), but also it differs from polystore's public API, like `.add()` has a different signature, and the group methods all take a explicit prefix.
604
636
 
605
637
  **Expires**: if you set the `EXPIRES = true`, then you are indicating that the client WILL manage the lifecycle of the data. This includes all methods, for example if an item is expired, then its key should not be returned in `.keys()`, it's value should not be returned in `.values()`, and the method `.has()` will return `false`. The good news is that you will always receive the option `expires`, which is either `null` (no expiration) or a `number` indicating the time when it will expire.
606
638
 
@@ -619,22 +651,15 @@ const value = await store.entries();
619
651
  // client.entries("hello:world:");
620
652
  ```
621
653
 
622
- > Note: all of the _group methods_ that return keys, should return them **with the prefix stripped**:
654
+ > Note: all of the _group methods_ that return keys, should return them **with the prefix**:
623
655
 
624
656
  ```js
625
- // Example if your client works around a simple object {}, we want to remove
626
- // the `prefix` from the beginning of the keys returned:
627
657
  client.keys = (prefix) => {
628
- return Object.keys(subStore)
629
- .filter((key) => key.startsWith(prefix))
630
- .map((key) => key.slice(prefix.length)); // <= Important!
658
+ // Filter the keys, and return them INCLUDING the prefix!
659
+ return Object.keys(subStore).filter((key) => key.startsWith(prefix));
631
660
  };
632
661
  ```
633
662
 
634
- You can and should just concatenate the `key + options.prefix`. We don't do it for two reasons: in some cases, like `.add()`, there's no key that we can use to concatenate, and also you might
635
-
636
- For example, if the user of `polystore` does `kv(client).prefix('hello:').get('a')`, your store will be directly called with `client.get('a', { prefix: 'hello:' })`. You can safely concatenate `options.prefix + key` since this library always ensures that the prefix is defined and defaults to `''`. We don't concatenate it interally because in some cases (like in `.add()`) it makes more sense that this is handled by the client as an optimization.
637
-
638
663
  While the signatures are different, you can check each entries on the output of Polystore API to see what is expected for the methods of the client to do, e.g. `.clear()` will remove all of the items that match the prefix (or everything if there's no prefix).
639
664
 
640
665
  **Example: Plain Object client**
package/src/index.d.ts CHANGED
@@ -1,21 +1,181 @@
1
1
  type Options = { expires?: number | string | null };
2
2
  type Value = null | string | { [key: string]: Value } | Value[];
3
3
 
4
- type Store = {
4
+ interface Store {
5
+ /**
6
+ * Save the data on an autogenerated key, can add expiration as well:
7
+ *
8
+ * ```js
9
+ * const key1 = await store.add("value1");
10
+ * const key2 = await store.add({ hello: "world" });
11
+ * const key3 = await store.add("value3", { expires: "1h" });
12
+ * ```
13
+ *
14
+ * **[→ Full .add() Docs](https://polystore.dev/documentation#add)**
15
+ */
16
+ add: (value: Value, options?: Options) => Promise<string>;
17
+
18
+ /**
19
+ * Save the data on the given key, can add expiration as well:
20
+ *
21
+ * ```js
22
+ * const key = await store.set("key1", "value1");
23
+ * await store.set("key2", { hello: "world" });
24
+ * await store.set("key3", "value3", { expires: "1h" });
25
+ * ```
26
+ *
27
+ * **[→ Full .set() Docs](https://polystore.dev/documentation#set)**
28
+ */
29
+ set: (key: string, value: Value, options?: Options) => Promise<string>;
30
+
31
+ /**
32
+ * Read a single value from the KV store:
33
+ *
34
+ * ```js
35
+ * const value1 = await store.get("key1");
36
+ * // null (doesn't exist or has expired)
37
+ * const value2 = await store.get("key2");
38
+ * // "value2"
39
+ * const value3 = await store.get("key3");
40
+ * // { hello: "world" }
41
+ * ```
42
+ *
43
+ * **[→ Full .get() Docs](https://polystore.dev/documentation#get)**
44
+ */
5
45
  get: (key: string) => Promise<Value>;
6
- add: (value: any, options?: Options) => Promise<string>;
7
- set: (key: string, value: any, options?: Options) => Promise<string>;
46
+
47
+ /**
48
+ * Check whether a key exists or not:
49
+ *
50
+ * ```js
51
+ * if (await store.has("key1")) { ... }
52
+ * ```
53
+ *
54
+ * If you are going to use the value, it's better to just read it:
55
+ *
56
+ * ```js
57
+ * const val = await store.get("key1");
58
+ * if (val) { ... }
59
+ * ```
60
+ *
61
+ *
62
+ * **[→ Full .has() Docs](https://polystore.dev/documentation#has)**
63
+ */
8
64
  has: (key: string) => Promise<boolean>;
65
+
66
+ /**
67
+ * Remove a single key and its value from the store:
68
+ *
69
+ * ```js
70
+ * const key = await store.del("key1");
71
+ * ```
72
+ *
73
+ * **[→ Full .del() Docs](https://polystore.dev/documentation#del)**
74
+ */
9
75
  del: (key: string) => Promise<null>;
10
76
 
77
+ /**
78
+ * Return an array of the entries, in the [key, value] format:
79
+ *
80
+ * ```js
81
+ * const entries = await store.entries();
82
+ * // [["key1", "value1"], ["key2", { hello: "world" }], ...]
83
+ *
84
+ * // To limit it to a given prefix, use `.prefix()`:
85
+ * const sessions = await store.prefix("session:").entries();
86
+ * ```
87
+ *
88
+ * **[→ Full .entries() Docs](https://polystore.dev/documentation#entries)**
89
+ */
90
+ entries: () => Promise<[key: string, value: Value][]>;
91
+
92
+ /**
93
+ * Return an array of the keys in the store:
94
+ *
95
+ * ```js
96
+ * const keys = await store.keys();
97
+ * // ["key1", "key2", ...]
98
+ *
99
+ * // To limit it to a given prefix, use `.prefix()`:
100
+ * const sessions = await store.prefix("session:").keys();
101
+ * ```
102
+ *
103
+ * **[→ Full .keys() Docs](https://polystore.dev/documentation#keys)**
104
+ */
11
105
  keys: () => Promise<string[]>;
12
- values: () => Promise<any[]>;
13
- entries: () => Promise<[key: string, value: any][]>;
14
106
 
15
- prefix: (prefix: string) => Store;
107
+ /**
108
+ * Return an array of the values in the store:
109
+ *
110
+ * ```js
111
+ * const values = await store.values();
112
+ * // ["value1", { hello: "world" }, ...]
113
+ *
114
+ * // To limit it to a given prefix, use `.prefix()`:
115
+ * const sessions = await store.prefix("session:").values();
116
+ * ```
117
+ *
118
+ * **[→ Full .values() Docs](https://polystore.dev/documentation#values)**
119
+ */
120
+ values: () => Promise<Value[]>;
121
+
122
+ /**
123
+ * Return an object with the keys:values in the store:
124
+ *
125
+ * ```js
126
+ * const obj = await store.all();
127
+ * // { key1: "value1", key2: { hello: "world" }, ... }
128
+ *
129
+ * // To limit it to a given prefix, use `.prefix()`:
130
+ * const sessions = await store.prefix("session:").all();
131
+ * ```
132
+ *
133
+ * **[→ Full .all() Docs](https://polystore.dev/documentation#all)**
134
+ */
135
+ all: () => Promise<{ [key: string]: Value }>;
16
136
 
137
+ /**
138
+ * Delete all of the records of the store:
139
+ *
140
+ * ```js
141
+ * await store.clear();
142
+ * ```
143
+ *
144
+ * It's useful for cache invalidation, clearing the data, and testing.
145
+ *
146
+ * **[→ Full .clear() Docs](https://polystore.dev/documentation#clear)**
147
+ */
17
148
  clear: () => Promise<null>;
149
+
150
+ /**
151
+ * Create a substore where all the keys are stored with
152
+ * the given prefix:
153
+ *
154
+ * ```js
155
+ * const session = store.prefix("session:");
156
+ * await session.set("key1", "value1");
157
+ * console.log(await session.entries()); // session.
158
+ * // [["key1", "value1"]]
159
+ * console.log(await store.entries()); // store.
160
+ * // [["session:key1", "value1"]]
161
+ * ```
162
+ *
163
+ * **[→ Full .prefix() Docs](https://polystore.dev/documentation#prefix)**
164
+ */
165
+ prefix: (prefix: string) => Store;
166
+
167
+ /**
168
+ * Stop the connection to the store, if any:
169
+ *
170
+ * ```js
171
+ * await session.set("key1", "value1");
172
+ * await store.close();
173
+ * await session.set("key2", "value2"); // error
174
+ * ```
175
+ *
176
+ * **[→ Full .close() Docs](https://polystore.dev/documentation#close)**
177
+ */
18
178
  close?: () => Promise<null>;
19
- };
179
+ }
20
180
 
21
181
  export default function (store?: any): Store;
package/src/index.js CHANGED
@@ -1,3 +1,8 @@
1
+ /**
2
+ * A number, or a string containing a number.
3
+ * @typedef {(number|string|object|array)} Value
4
+ */
5
+
1
6
  import clients from "./clients/index.js";
2
7
  import { createId, isClass, parse } from "./utils.js";
3
8
 
@@ -151,7 +156,7 @@ class Store {
151
156
  *
152
157
  * ```js
153
158
  * const value1 = await store.get("key1");
154
- * // null (it doesn't exist, or it has expired)
159
+ * // null (doesn't exist or has expired)
155
160
  * const value2 = await store.get("key2");
156
161
  * // "value2"
157
162
  * const value3 = await store.get("key3");
@@ -302,6 +307,34 @@ class Store {
302
307
  .map(([key, data]) => [key, data.value]);
303
308
  }
304
309
 
310
+ // #region .keys()
311
+ /**
312
+ * Return an array of the keys in the store:
313
+ *
314
+ * ```js
315
+ * const keys = await store.keys();
316
+ * // ["key1", "key2", ...]
317
+ *
318
+ * // To limit it to a given prefix, use `.prefix()`:
319
+ * const sessions = await store.prefix("session:").keys();
320
+ * ```
321
+ *
322
+ * **[→ Full .keys() Docs](https://polystore.dev/documentation#keys)**
323
+ * @returns {Promise<string[]>}
324
+ */
325
+ async keys() {
326
+ await this.promise;
327
+
328
+ if (this.client.keys) {
329
+ const list = await this.client.keys(this.PREFIX);
330
+ if (!this.PREFIX) return list;
331
+ return list.map((k) => k.slice(this.PREFIX.length));
332
+ }
333
+
334
+ const entries = await this.entries();
335
+ return entries.map((e) => e[0]);
336
+ }
337
+
305
338
  // #region .values()
306
339
  /**
307
340
  * Return an array of the values in the store:
@@ -347,34 +380,6 @@ class Store {
347
380
  return entries.map((e) => e[1]);
348
381
  }
349
382
 
350
- // #region .keys()
351
- /**
352
- * Return an array of the keys in the store:
353
- *
354
- * ```js
355
- * const keys = await store.keys();
356
- * // ["key1", "key2", ...]
357
- *
358
- * // To limit it to a given prefix, use `.prefix()`:
359
- * const sessions = await store.prefix("session:").keys();
360
- * ```
361
- *
362
- * **[→ Full .keys() Docs](https://polystore.dev/documentation#keys)**
363
- * @returns {Promise<string[]>}
364
- */
365
- async keys() {
366
- await this.promise;
367
-
368
- if (this.client.keys) {
369
- const list = await this.client.keys(this.PREFIX);
370
- if (!this.PREFIX) return list;
371
- return list.map((k) => k.slice(this.PREFIX.length));
372
- }
373
-
374
- const entries = await this.entries();
375
- return entries.map((e) => e[0]);
376
- }
377
-
378
383
  // #region .all()
379
384
  /**
380
385
  * Return an object with the keys:values in the store:
@@ -458,6 +463,18 @@ class Store {
458
463
  }
459
464
 
460
465
  // #region .close()
466
+ /**
467
+ * Stop the connection to the store, if any:
468
+ *
469
+ * ```js
470
+ * await session.set("key1", "value1");
471
+ * await store.close();
472
+ * await session.set("key2", "value2"); // error
473
+ * ```
474
+ *
475
+ * **[→ Full .close() Docs](https://polystore.dev/documentation#close)**
476
+ * @returns {Store}
477
+ */
461
478
  async close() {
462
479
  await this.promise;
463
480
 
package/src/test/setup.js CHANGED
@@ -2,11 +2,15 @@ import * as util from "util";
2
2
 
3
3
  // ref: https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom
4
4
  // ref: https://github.com/jsdom/jsdom/issues/2524
5
- Object.defineProperty(window, "TextEncoder", {
6
- writable: true,
7
- value: util.TextEncoder,
8
- });
9
- Object.defineProperty(window, "TextDecoder", {
10
- writable: true,
11
- value: util.TextDecoder,
12
- });
5
+ if (typeof TextEncoder === "undefined") {
6
+ Object.defineProperty(window, "TextEncoder", {
7
+ writable: true,
8
+ value: util.TextEncoder,
9
+ });
10
+ }
11
+ if (typeof TextDecoder === "undefined") {
12
+ Object.defineProperty(window, "TextDecoder", {
13
+ writable: true,
14
+ value: util.TextDecoder,
15
+ });
16
+ }