@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 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.13",
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.
@@ -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 {};