@webreflection/utils 0.2.13 → 0.2.15
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/README.md +2 -0
- package/package.json +16 -1
- package/src/README.md +61 -0
- package/src/iterable.js +37 -0
- package/src/json-storage.js +167 -0
- package/types/iterable.d.ts +13 -0
- package/types/json-storage.d.ts +78 -0
package/README.md
CHANGED
|
@@ -11,6 +11,8 @@ A [collection](./src/) of utility functions:
|
|
|
11
11
|
* **[ascii](https://github.com/WebReflection/utils/tree/main/src#ascii)** - basic string to buffer conversion (without validation)
|
|
12
12
|
* **[bound-once](https://github.com/WebReflection/utils/tree/main/src#bound-once)** - to retrieve unique bound methods per realm
|
|
13
13
|
* **[bound](https://github.com/WebReflection/utils/tree/main/src#bound)** - to retrieve one-off bound methods
|
|
14
|
+
* **[iterable](https://github.com/WebReflection/utils/tree/main/src#iterable)** - to make plain objects iterable as `Object.entries(object)` pairs, without touching objects that already are iterable
|
|
15
|
+
* **[json-storage](https://github.com/WebReflection/utils/tree/main/src#json-storage)** - to use `localStorage` or `sessionStorage` through a JSON-aware, iterable, *Map* friendly API
|
|
14
16
|
* **[shared-array-buffer](https://github.com/WebReflection/utils/tree/main/src#shared-array-buffer)** - to simulate *SAB* when not available
|
|
15
17
|
* **[sticky](https://github.com/WebReflection/utils/tree/main/src#sticky)** - to stick once per realm anything useful
|
|
16
18
|
* **[with-resolvers](https://github.com/WebReflection/utils/tree/main/src#with-resolvers)** - a self bound `Promise.withResolver()` for older runtimes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webreflection/utils",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.15",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"types": {
|
|
6
6
|
"./all": "./types/all.d.ts",
|
|
@@ -28,6 +28,14 @@
|
|
|
28
28
|
"types": "./types/bound.d.ts",
|
|
29
29
|
"import": "./src/bound.js"
|
|
30
30
|
},
|
|
31
|
+
"./iterable": {
|
|
32
|
+
"types": "./types/iterable.d.ts",
|
|
33
|
+
"import": "./src/iterable.js"
|
|
34
|
+
},
|
|
35
|
+
"./json-storage": {
|
|
36
|
+
"types": "./types/json-storage.d.ts",
|
|
37
|
+
"import": "./src/json-storage.js"
|
|
38
|
+
},
|
|
31
39
|
"./shared-array-buffer": {
|
|
32
40
|
"types": "./types/shared-array-buffer.d.ts",
|
|
33
41
|
"import": "./src/shared-array-buffer.js"
|
|
@@ -47,6 +55,8 @@
|
|
|
47
55
|
"ascii",
|
|
48
56
|
"bound-once",
|
|
49
57
|
"bound",
|
|
58
|
+
"iterable",
|
|
59
|
+
"json-storage",
|
|
50
60
|
"shared-array-buffer",
|
|
51
61
|
"sticky",
|
|
52
62
|
"with-resolvers"
|
|
@@ -79,6 +89,11 @@
|
|
|
79
89
|
"url": "https://github.com/WebReflection/utils/issues"
|
|
80
90
|
},
|
|
81
91
|
"homepage": "https://github.com/WebReflection/utils#readme",
|
|
92
|
+
"overrides": {
|
|
93
|
+
"c8": {
|
|
94
|
+
"yargs": "^18.0.0"
|
|
95
|
+
}
|
|
96
|
+
},
|
|
82
97
|
"devDependencies": {
|
|
83
98
|
"c8": "^11.0.0",
|
|
84
99
|
"typescript": "^6.0.3"
|
package/src/README.md
CHANGED
|
@@ -70,6 +70,67 @@ resolve(4);
|
|
|
70
70
|
|
|
71
71
|
The **bound-once** variant ensures that repeated accesses, such as `boundOnce(Promise).all`, always return the same bound method.
|
|
72
72
|
|
|
73
|
+
|
|
74
|
+
## iterable
|
|
75
|
+
|
|
76
|
+
Ensures an object can be consumed by `for...of`, spread, `Array.from`, and
|
|
77
|
+
other iterable-aware APIs.
|
|
78
|
+
|
|
79
|
+
```js
|
|
80
|
+
import iterable from '@webreflection/utils/iterable';
|
|
81
|
+
|
|
82
|
+
const query = iterable({ page: 1, perPage: 20 });
|
|
83
|
+
|
|
84
|
+
console.log([...query]);
|
|
85
|
+
// [['page', 1], ['perPage', 20]]
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
If the object already defines or inherits `Symbol.iterator`, it is returned
|
|
89
|
+
unchanged. Otherwise, the same object receives a configurable own
|
|
90
|
+
`Symbol.iterator` method that yields `Object.entries(ref)`.
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
## json-storage
|
|
94
|
+
|
|
95
|
+
A small *Map* like facade over `localStorage` by default, or `sessionStorage`
|
|
96
|
+
when requested. Values are serialized with `JSON.stringify` on write and parsed
|
|
97
|
+
with `JSON.parse` on read, so callers can store structured data without
|
|
98
|
+
manually converting every value.
|
|
99
|
+
|
|
100
|
+
```js
|
|
101
|
+
import JSONStorage from '@webreflection/utils/json-storage';
|
|
102
|
+
|
|
103
|
+
const preferences = new JSONStorage;
|
|
104
|
+
|
|
105
|
+
preferences.set('theme', { dark: true });
|
|
106
|
+
|
|
107
|
+
console.log(preferences.get('theme').dark);
|
|
108
|
+
// true
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The API follows familiar `Map` names where they make sense: `get`, `set`,
|
|
112
|
+
`has`, `delete`, `clear`, `entries`, `keys`, `values`, and default iteration.
|
|
113
|
+
Missing keys return `undefined`, while `delete(key)` reports whether the key was
|
|
114
|
+
present.
|
|
115
|
+
|
|
116
|
+
```js
|
|
117
|
+
const cart = new JSONStorage(JSONStorage.SESSION);
|
|
118
|
+
|
|
119
|
+
const items = cart.getOrInsert('items', []);
|
|
120
|
+
items.push('book');
|
|
121
|
+
cart.set('items', items);
|
|
122
|
+
|
|
123
|
+
for (const [key, value] of cart) {
|
|
124
|
+
console.log(key, value);
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Use `getOrInsert(key, value)` to create a value only when the key is absent, or
|
|
129
|
+
`getOrInsertComputed(key, callback)` when the initial value should be computed
|
|
130
|
+
from the key. A second constructor argument can replace the native *JSON* API as
|
|
131
|
+
long as it provides compatible `parse(source)` and `stringify(value)` methods.
|
|
132
|
+
|
|
133
|
+
|
|
73
134
|
## shared-array-buffer
|
|
74
135
|
|
|
75
136
|
This utility provides an unobtrusive *SAB* (*SharedArrayBuffer*) shim based on the default *ArrayBuffer*, with `grow(length)` and `growable` additions.
|
package/src/iterable.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/** @type {typeof Symbol.iterator} */
|
|
4
|
+
const iterator = Symbol.iterator;
|
|
5
|
+
|
|
6
|
+
const { defineProperty: dp, entries } = Object;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @this {object}
|
|
10
|
+
* @returns {Generator<[string, any], void, unknown>}
|
|
11
|
+
*/
|
|
12
|
+
function* value() {
|
|
13
|
+
yield* entries(this);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const configurable = true;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Ensures an object is iterable.
|
|
20
|
+
*
|
|
21
|
+
* If `ref` already defines or inherits `Symbol.iterator`, it is returned
|
|
22
|
+
* unchanged. Otherwise, the same object is augmented with `Symbol.iterator`
|
|
23
|
+
* yielding the entries produced by `Object.entries(ref)`.
|
|
24
|
+
*
|
|
25
|
+
* @typedef {{
|
|
26
|
+
* <T extends Iterable<unknown>>(ref: T): T;
|
|
27
|
+
* <T extends object>(ref: T): T & Iterable<[string, T[keyof T]]>;
|
|
28
|
+
* }} EnsureIterable
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {object} ref
|
|
33
|
+
* @returns {object}
|
|
34
|
+
*/
|
|
35
|
+
const iterable = ref => iterator in ref ? ref : dp(ref, iterator, { configurable, value });
|
|
36
|
+
|
|
37
|
+
export default /** @type {EnsureIterable} */(iterable);
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
const { entries, keys, values } = Object;
|
|
4
|
+
|
|
5
|
+
/** @type {typeof Symbol.iterator} */
|
|
6
|
+
const iterator = Symbol.iterator;
|
|
7
|
+
|
|
8
|
+
const LOCAL = 'local';
|
|
9
|
+
const SESSION = 'session';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @typedef {typeof LOCAL | typeof SESSION} JSONStorageKind
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @template Value
|
|
17
|
+
* @typedef {{
|
|
18
|
+
* parse(source: string): Value,
|
|
19
|
+
* stringify(value: Value): string
|
|
20
|
+
* }} JSONStorageAPI
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @typedef {{
|
|
25
|
+
* clear(): void,
|
|
26
|
+
* getItem(key: string): string | null,
|
|
27
|
+
* removeItem(key: string): void,
|
|
28
|
+
* setItem(key: string, value: string): void,
|
|
29
|
+
* [Symbol.iterator](): Generator<[string, string], void, unknown>,
|
|
30
|
+
* }} StorageAPI
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @template Value
|
|
35
|
+
* @param {JSONStorage<Value>} self
|
|
36
|
+
* @param {string} key
|
|
37
|
+
* @param {Value} value
|
|
38
|
+
* @returns {Value}
|
|
39
|
+
*/
|
|
40
|
+
const add = (self, key, value) => {
|
|
41
|
+
self.set(key, value);
|
|
42
|
+
return value;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Map-like facade over `localStorage` or `sessionStorage` that stores values
|
|
47
|
+
* through a JSON-compatible `parse`/`stringify` pair.
|
|
48
|
+
*
|
|
49
|
+
* @template [Value=unknown]
|
|
50
|
+
* @implements {Iterable<[string, Value]>}
|
|
51
|
+
*/
|
|
52
|
+
export default class JSONStorage {
|
|
53
|
+
/** @type {typeof LOCAL} */
|
|
54
|
+
static LOCAL = LOCAL;
|
|
55
|
+
|
|
56
|
+
/** @type {typeof SESSION} */
|
|
57
|
+
static SESSION = SESSION;
|
|
58
|
+
|
|
59
|
+
/** @type {StorageAPI} */
|
|
60
|
+
#storage;
|
|
61
|
+
|
|
62
|
+
/** @type {(value: Value) => string} */
|
|
63
|
+
#stringify;
|
|
64
|
+
|
|
65
|
+
/** @type {(source: string) => Value} */
|
|
66
|
+
#parse;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @param {JSONStorageKind} [storage=LOCAL]
|
|
70
|
+
* @param {JSONStorageAPI<Value>} [json=JSON]
|
|
71
|
+
*/
|
|
72
|
+
constructor(storage = LOCAL, { parse, stringify } = /** @type {JSONStorageAPI<Value>} */ (JSON)) {
|
|
73
|
+
// @ts-ignore
|
|
74
|
+
this.#storage = storage === LOCAL ? localStorage : sessionStorage;
|
|
75
|
+
this.#stringify = stringify;
|
|
76
|
+
this.#parse = parse;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** @returns {void} */
|
|
80
|
+
clear() {
|
|
81
|
+
this.#storage.clear();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @param {string} key
|
|
86
|
+
* @returns {boolean}
|
|
87
|
+
*/
|
|
88
|
+
delete(key) {
|
|
89
|
+
const had = this.has(key);
|
|
90
|
+
if (had) this.#storage.removeItem(key);
|
|
91
|
+
return had;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @param {string} key
|
|
96
|
+
* @returns {Value | undefined}
|
|
97
|
+
*/
|
|
98
|
+
get(key) {
|
|
99
|
+
const value = this.#storage.getItem(key);
|
|
100
|
+
return typeof value === 'string' ? this.#parse(value) : void 0;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* @param {string} key
|
|
105
|
+
* @param {Value} value
|
|
106
|
+
* @returns {Value}
|
|
107
|
+
*/
|
|
108
|
+
getOrInsert(key, value) {
|
|
109
|
+
const current = this.get(key);
|
|
110
|
+
return current !== void 0 ? current : add(this, key, value);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* @template {string} Key
|
|
115
|
+
* @param {Key} key
|
|
116
|
+
* @param {(key: Key) => Value} callback
|
|
117
|
+
* @returns {Value}
|
|
118
|
+
*/
|
|
119
|
+
getOrInsertComputed(key, callback) {
|
|
120
|
+
const current = this.get(key);
|
|
121
|
+
return current !== void 0 ? current : add(this, key, callback(key));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* @param {string} key
|
|
126
|
+
* @returns {boolean}
|
|
127
|
+
*/
|
|
128
|
+
has(key) {
|
|
129
|
+
return this.#storage.getItem(key) != null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* @param {string} key
|
|
134
|
+
* @param {Value} value
|
|
135
|
+
* @returns {this}
|
|
136
|
+
*/
|
|
137
|
+
set(key, value) {
|
|
138
|
+
this.#storage.setItem(key, this.#stringify(value));
|
|
139
|
+
return this;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** @returns {Generator<[string, Value], void, unknown>} */
|
|
143
|
+
*entries() {
|
|
144
|
+
yield* this[iterator]();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** @returns {Generator<string, void, unknown>} */
|
|
148
|
+
*keys() {
|
|
149
|
+
yield* keys(this.#storage);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** @returns {Generator<Value, void, unknown>} */
|
|
153
|
+
*values() {
|
|
154
|
+
for (const value of values(this.#storage)) {
|
|
155
|
+
// @ts-ignore
|
|
156
|
+
yield this.#parse(value);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** @returns {Generator<[string, Value], void, unknown>} */
|
|
161
|
+
*[iterator]() {
|
|
162
|
+
for (const [key, value] of entries(this.#storage)) {
|
|
163
|
+
// @ts-ignore
|
|
164
|
+
yield [key, this.#parse(value)];
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
declare const _default: EnsureIterable;
|
|
2
|
+
export default _default;
|
|
3
|
+
/**
|
|
4
|
+
* Ensures an object is iterable.
|
|
5
|
+
*
|
|
6
|
+
* If `ref` already defines or inherits `Symbol.iterator`, it is returned
|
|
7
|
+
* unchanged. Otherwise, the same object is augmented with `Symbol.iterator`
|
|
8
|
+
* yielding the entries produced by `Object.entries(ref)`.
|
|
9
|
+
*/
|
|
10
|
+
export type EnsureIterable = {
|
|
11
|
+
<T extends Iterable<unknown>>(ref: T): T;
|
|
12
|
+
<T extends object>(ref: T): T & Iterable<[string, T[keyof T]]>;
|
|
13
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map-like facade over `localStorage` or `sessionStorage` that stores values
|
|
3
|
+
* through a JSON-compatible `parse`/`stringify` pair.
|
|
4
|
+
*
|
|
5
|
+
* @template [Value=unknown]
|
|
6
|
+
* @implements {Iterable<[string, Value]>}
|
|
7
|
+
*/
|
|
8
|
+
export default class JSONStorage<Value = unknown> implements Iterable<[string, Value]> {
|
|
9
|
+
/** @type {typeof LOCAL} */
|
|
10
|
+
static LOCAL: typeof LOCAL;
|
|
11
|
+
/** @type {typeof SESSION} */
|
|
12
|
+
static SESSION: typeof SESSION;
|
|
13
|
+
/**
|
|
14
|
+
* @param {JSONStorageKind} [storage=LOCAL]
|
|
15
|
+
* @param {JSONStorageAPI<Value>} [json=JSON]
|
|
16
|
+
*/
|
|
17
|
+
constructor(storage?: JSONStorageKind, { parse, stringify }?: JSONStorageAPI<Value>);
|
|
18
|
+
/** @returns {void} */
|
|
19
|
+
clear(): void;
|
|
20
|
+
/**
|
|
21
|
+
* @param {string} key
|
|
22
|
+
* @returns {boolean}
|
|
23
|
+
*/
|
|
24
|
+
delete(key: string): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* @param {string} key
|
|
27
|
+
* @returns {Value | undefined}
|
|
28
|
+
*/
|
|
29
|
+
get(key: string): Value | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* @param {string} key
|
|
32
|
+
* @param {Value} value
|
|
33
|
+
* @returns {Value}
|
|
34
|
+
*/
|
|
35
|
+
getOrInsert(key: string, value: Value): Value;
|
|
36
|
+
/**
|
|
37
|
+
* @template {string} Key
|
|
38
|
+
* @param {Key} key
|
|
39
|
+
* @param {(key: Key) => Value} callback
|
|
40
|
+
* @returns {Value}
|
|
41
|
+
*/
|
|
42
|
+
getOrInsertComputed<Key extends string>(key: Key, callback: (key: Key) => Value): Value;
|
|
43
|
+
/**
|
|
44
|
+
* @param {string} key
|
|
45
|
+
* @returns {boolean}
|
|
46
|
+
*/
|
|
47
|
+
has(key: string): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* @param {string} key
|
|
50
|
+
* @param {Value} value
|
|
51
|
+
* @returns {this}
|
|
52
|
+
*/
|
|
53
|
+
set(key: string, value: Value): this;
|
|
54
|
+
/** @returns {Generator<[string, Value], void, unknown>} */
|
|
55
|
+
entries(): Generator<[string, Value], void, unknown>;
|
|
56
|
+
/** @returns {Generator<string, void, unknown>} */
|
|
57
|
+
keys(): Generator<string, void, unknown>;
|
|
58
|
+
/** @returns {Generator<Value, void, unknown>} */
|
|
59
|
+
values(): Generator<Value, void, unknown>;
|
|
60
|
+
/** @returns {Generator<[string, Value], void, unknown>} */
|
|
61
|
+
[Symbol.iterator](): Generator<[string, Value], void, unknown>;
|
|
62
|
+
#private;
|
|
63
|
+
}
|
|
64
|
+
export type JSONStorageKind = typeof LOCAL | typeof SESSION;
|
|
65
|
+
export type JSONStorageAPI<Value> = {
|
|
66
|
+
parse(source: string): Value;
|
|
67
|
+
stringify(value: Value): string;
|
|
68
|
+
};
|
|
69
|
+
export type StorageAPI = {
|
|
70
|
+
clear(): void;
|
|
71
|
+
getItem(key: string): string | null;
|
|
72
|
+
removeItem(key: string): void;
|
|
73
|
+
setItem(key: string, value: string): void;
|
|
74
|
+
[Symbol.iterator](): Generator<[string, string], void, unknown>;
|
|
75
|
+
};
|
|
76
|
+
declare const LOCAL: "local";
|
|
77
|
+
declare const SESSION: "session";
|
|
78
|
+
export {};
|