@naturalcycles/js-lib 15.44.0 → 15.46.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/dist/env.d.ts CHANGED
@@ -12,3 +12,9 @@ export declare function isServerSide(): boolean;
12
12
  * Will return `false` in Node.js.
13
13
  */
14
14
  export declare function isClientSide(): boolean;
15
+ /**
16
+ * Almost the same as isServerSide()
17
+ * (isServerSide should return true for Node),
18
+ * but detects Node specifically (not Deno, not Bun, etc).
19
+ */
20
+ export declare function isNode(): boolean;
package/dist/env.js CHANGED
@@ -17,3 +17,11 @@ export function isClientSide() {
17
17
  // oxlint-disable-next-line unicorn/prefer-global-this
18
18
  return typeof window !== 'undefined' && !!window?.document;
19
19
  }
20
+ /**
21
+ * Almost the same as isServerSide()
22
+ * (isServerSide should return true for Node),
23
+ * but detects Node specifically (not Deno, not Bun, etc).
24
+ */
25
+ export function isNode() {
26
+ return typeof process !== 'undefined' && process?.release?.name === 'node';
27
+ }
@@ -20,9 +20,8 @@ export interface KeySortedMapOptions<K> {
20
20
  * @experimental
21
21
  */
22
22
  export declare class KeySortedMap<K, V> implements Map<K, V> {
23
- opt: KeySortedMapOptions<K>;
23
+ #private;
24
24
  private readonly map;
25
- private readonly sortedKeys;
26
25
  constructor(entries?: [K, V][], opt?: KeySortedMapOptions<K>);
27
26
  /**
28
27
  * Convenience way to create KeySortedMap from object.
@@ -52,25 +51,30 @@ export declare class KeySortedMap<K, V> implements Map<K, V> {
52
51
  values(): MapIterator<V>;
53
52
  entries(): MapIterator<[K, V]>;
54
53
  [Symbol.iterator](): MapIterator<[K, V]>;
54
+ toString(): string;
55
55
  [Symbol.toStringTag]: string;
56
56
  /**
57
57
  * Zero-allocation callbacks over sorted data (faster than spreading to arrays).
58
58
  */
59
59
  forEach(cb: (value: V, key: K, map: Map<K, V>) => void, thisArg?: any): void;
60
- /**
61
- * Convenience methods that MATERIALIZE arrays (if you really want arrays).
62
- * These allocate; use iterators/forEach for maximum performance.
63
- */
64
- keysArray(): K[];
65
- valuesArray(): V[];
66
- entriesArray(): [K, V][];
67
- /** Fast helpers */
68
- firstKey(): K | undefined;
69
- lastKey(): K | undefined;
70
- firstEntry(): [K, V] | undefined;
71
- lastEntry(): [K, V] | undefined;
60
+ firstKeyOrUndefined(): K | undefined;
61
+ firstKey(): K;
62
+ lastKeyOrUndefined(): K | undefined;
63
+ lastKey(): K;
64
+ firstValueOrUndefined(): V | undefined;
65
+ firstValue(): V;
66
+ lastValueOrUndefined(): V | undefined;
67
+ lastValue(): V;
68
+ firstEntryOrUndefined(): [K, V] | undefined;
69
+ firstEntry(): [K, V];
70
+ lastEntryOrUndefined(): [K, V] | undefined;
71
+ lastEntry(): [K, V];
72
72
  toJSON(): Record<string, V>;
73
73
  toObject(): Record<string, V>;
74
+ /**
75
+ * Clones the KeySortedMap into ordinary Map.
76
+ */
77
+ toMap(): Map<K, V>;
74
78
  /**
75
79
  * lowerBound: first index i s.t. keys[i] >= target
76
80
  */
@@ -1,3 +1,4 @@
1
+ import { _assert } from '../error/index.js';
1
2
  /**
2
3
  * Maintains sorted array of keys.
3
4
  * Sorts **on insertion**, not on retrieval.
@@ -10,15 +11,15 @@
10
11
  * @experimental
11
12
  */
12
13
  export class KeySortedMap {
13
- opt;
14
14
  map;
15
- sortedKeys;
15
+ #sortedKeys;
16
16
  constructor(entries = [], opt = {}) {
17
- this.opt = opt;
17
+ this.#comparator = opt.comparator;
18
18
  this.map = new Map(entries);
19
- this.sortedKeys = [...this.map.keys()];
19
+ this.#sortedKeys = [...this.map.keys()];
20
20
  this.sortKeys();
21
21
  }
22
+ #comparator;
22
23
  /**
23
24
  * Convenience way to create KeySortedMap from object.
24
25
  */
@@ -30,7 +31,7 @@ export class KeySortedMap {
30
31
  }
31
32
  clear() {
32
33
  this.map.clear();
33
- this.sortedKeys.length = 0;
34
+ this.#sortedKeys.length = 0;
34
35
  }
35
36
  has(key) {
36
37
  return this.map.has(key);
@@ -44,7 +45,7 @@ export class KeySortedMap {
44
45
  setMany(obj) {
45
46
  for (const [k, v] of Object.entries(obj)) {
46
47
  this.map.set(k, v);
47
- this.sortedKeys.push(k);
48
+ this.#sortedKeys.push(k);
48
49
  }
49
50
  // Resort all at once
50
51
  this.sortKeys();
@@ -63,7 +64,7 @@ export class KeySortedMap {
63
64
  // Find insertion index (lower_bound).
64
65
  const i = this.lowerBound(key);
65
66
  // Only insert into keys when actually new.
66
- this.sortedKeys.splice(i, 0, key);
67
+ this.#sortedKeys.splice(i, 0, key);
67
68
  this.map.set(key, value);
68
69
  return this;
69
70
  }
@@ -77,15 +78,15 @@ export class KeySortedMap {
77
78
  // Remove from keys using binary search to avoid O(n) find.
78
79
  const i = this.lowerBound(key);
79
80
  // Because key existed, it must be at i.
80
- if (i < this.sortedKeys.length && this.sortedKeys[i] === key) {
81
- this.sortedKeys.splice(i, 1);
81
+ if (i < this.#sortedKeys.length && this.#sortedKeys[i] === key) {
82
+ this.#sortedKeys.splice(i, 1);
82
83
  }
83
84
  else {
84
85
  // Extremely unlikely if external mutation happened; safe guard.
85
86
  // Fall back to linear search (shouldn't happen).
86
- const j = this.sortedKeys.indexOf(key);
87
+ const j = this.#sortedKeys.indexOf(key);
87
88
  if (j !== -1)
88
- this.sortedKeys.splice(j, 1);
89
+ this.#sortedKeys.splice(j, 1);
89
90
  }
90
91
  return true;
91
92
  }
@@ -93,94 +94,111 @@ export class KeySortedMap {
93
94
  * Iterables (Map-compatible), all in sorted order.
94
95
  */
95
96
  *keys() {
96
- for (let i = 0; i < this.sortedKeys.length; i++) {
97
- yield this.sortedKeys[i];
97
+ for (const key of this.#sortedKeys) {
98
+ yield key;
98
99
  }
99
100
  }
100
101
  *values() {
101
- for (let i = 0; i < this.sortedKeys.length; i++) {
102
- yield this.map.get(this.sortedKeys[i]);
102
+ for (const key of this.#sortedKeys) {
103
+ yield this.map.get(key);
103
104
  }
104
105
  }
105
106
  *entries() {
106
- for (let i = 0; i < this.sortedKeys.length; i++) {
107
- const k = this.sortedKeys[i];
107
+ for (const k of this.#sortedKeys) {
108
108
  yield [k, this.map.get(k)];
109
109
  }
110
110
  }
111
111
  [Symbol.iterator]() {
112
112
  return this.entries();
113
113
  }
114
+ toString() {
115
+ console.log('toString called !!!!!!!!!!!!!!!!!!!!!');
116
+ return 'abc';
117
+ }
114
118
  [Symbol.toStringTag] = 'KeySortedMap';
115
119
  /**
116
120
  * Zero-allocation callbacks over sorted data (faster than spreading to arrays).
117
121
  */
118
122
  forEach(cb, thisArg) {
119
- const m = this.map;
120
- for (let i = 0; i < this.sortedKeys.length; i++) {
121
- const k = this.sortedKeys[i];
122
- cb.call(thisArg, m.get(k), k, this);
123
+ const { map } = this;
124
+ for (const k of this.#sortedKeys) {
125
+ cb.call(thisArg, map.get(k), k, this);
123
126
  }
124
127
  }
125
- /**
126
- * Convenience methods that MATERIALIZE arrays (if you really want arrays).
127
- * These allocate; use iterators/forEach for maximum performance.
128
- */
129
- keysArray() {
130
- return this.sortedKeys.slice();
131
- }
132
- valuesArray() {
133
- // oxlint-disable-next-line unicorn/no-new-array
134
- const a = Array(this.sortedKeys.length);
135
- for (let i = 0; i < this.sortedKeys.length; i++) {
136
- a[i] = this.map.get(this.sortedKeys[i]);
137
- }
138
- return a;
139
- }
140
- entriesArray() {
141
- // oxlint-disable-next-line unicorn/no-new-array
142
- const out = Array(this.sortedKeys.length);
143
- for (let i = 0; i < this.sortedKeys.length; i++) {
144
- const k = this.sortedKeys[i];
145
- out[i] = [k, this.map.get(k)];
146
- }
147
- return out;
128
+ firstKeyOrUndefined() {
129
+ return this.#sortedKeys[0];
148
130
  }
149
- /** Fast helpers */
150
131
  firstKey() {
151
- return this.sortedKeys[0];
132
+ _assert(this.#sortedKeys.length, 'Map.firstKey called on empty map');
133
+ return this.#sortedKeys[0];
134
+ }
135
+ lastKeyOrUndefined() {
136
+ return this.#sortedKeys.length ? this.#sortedKeys[this.#sortedKeys.length - 1] : undefined;
152
137
  }
153
138
  lastKey() {
154
- return this.sortedKeys.length ? this.sortedKeys[this.sortedKeys.length - 1] : undefined;
139
+ _assert(this.#sortedKeys.length, 'Map.lastKey called on empty map');
140
+ return this.#sortedKeys[this.#sortedKeys.length - 1];
141
+ }
142
+ firstValueOrUndefined() {
143
+ return this.map.get(this.#sortedKeys[0]);
144
+ }
145
+ firstValue() {
146
+ _assert(this.#sortedKeys.length, 'Map.firstValue called on empty map');
147
+ return this.map.get(this.#sortedKeys[0]);
148
+ }
149
+ lastValueOrUndefined() {
150
+ return this.#sortedKeys.length
151
+ ? this.map.get(this.#sortedKeys[this.#sortedKeys.length - 1])
152
+ : undefined;
153
+ }
154
+ lastValue() {
155
+ _assert(this.#sortedKeys.length, 'Map.lastValue called on empty map');
156
+ return this.map.get(this.#sortedKeys[this.#sortedKeys.length - 1]);
157
+ }
158
+ firstEntryOrUndefined() {
159
+ if (!this.#sortedKeys.length)
160
+ return;
161
+ const k = this.#sortedKeys[0];
162
+ return [k, this.map.get(k)];
155
163
  }
156
164
  firstEntry() {
157
- if (!this.sortedKeys.length)
165
+ _assert(this.#sortedKeys.length, 'Map.firstEntry called on empty map');
166
+ const k = this.#sortedKeys[0];
167
+ return [k, this.map.get(k)];
168
+ }
169
+ lastEntryOrUndefined() {
170
+ if (!this.#sortedKeys.length)
158
171
  return;
159
- const k = this.sortedKeys[0];
172
+ const k = this.#sortedKeys[this.#sortedKeys.length - 1];
160
173
  return [k, this.map.get(k)];
161
174
  }
162
175
  lastEntry() {
163
- if (!this.sortedKeys.length)
164
- return;
165
- const k = this.sortedKeys[this.sortedKeys.length - 1];
176
+ _assert(this.#sortedKeys.length, 'Map.lastEntry called on empty map');
177
+ const k = this.#sortedKeys[this.#sortedKeys.length - 1];
166
178
  return [k, this.map.get(k)];
167
179
  }
168
180
  toJSON() {
169
181
  return this.toObject();
170
182
  }
171
183
  toObject() {
172
- return Object.fromEntries(this.map);
184
+ return Object.fromEntries(this.entries());
185
+ }
186
+ /**
187
+ * Clones the KeySortedMap into ordinary Map.
188
+ */
189
+ toMap() {
190
+ return new Map(this.entries());
173
191
  }
174
192
  /**
175
193
  * lowerBound: first index i s.t. keys[i] >= target
176
194
  */
177
195
  lowerBound(target) {
178
196
  let lo = 0;
179
- let hi = this.sortedKeys.length;
197
+ let hi = this.#sortedKeys.length;
180
198
  while (lo < hi) {
181
199
  // oxlint-disable-next-line no-bitwise
182
200
  const mid = (lo + hi) >>> 1;
183
- if (this.sortedKeys[mid] < target) {
201
+ if (this.#sortedKeys[mid] < target) {
184
202
  lo = mid + 1;
185
203
  }
186
204
  else {
@@ -190,6 +208,6 @@ export class KeySortedMap {
190
208
  return lo;
191
209
  }
192
210
  sortKeys() {
193
- this.sortedKeys.sort(this.opt.comparator);
211
+ this.#sortedKeys.sort(this.#comparator);
194
212
  }
195
213
  }
@@ -0,0 +1,72 @@
1
+ import type { Comparator } from '../types.js';
2
+ export interface KeySortedMapOptions<K> {
3
+ /**
4
+ * Defaults to undefined.
5
+ * Undefined (default comparator) works well for String keys.
6
+ * For Number keys - use comparators.numericAsc (or desc),
7
+ * otherwise sorting will be wrong (lexicographic).
8
+ */
9
+ comparator?: Comparator<K>;
10
+ }
11
+ /**
12
+ * Maintains sorted array of keys.
13
+ * Sorts **data access**, not on insertion.
14
+ *
15
+ * @experimental
16
+ */
17
+ export declare class KeySortedMap2<K, V> implements Map<K, V> {
18
+ #private;
19
+ private readonly map;
20
+ private readonly maybeSortedKeys;
21
+ private keysAreSorted;
22
+ constructor(entries?: [K, V][], opt?: KeySortedMapOptions<K>);
23
+ /**
24
+ * Convenience way to create KeySortedMap from object.
25
+ */
26
+ static of<V>(obj: Record<any, V>): KeySortedMap2<string, V>;
27
+ get size(): number;
28
+ clear(): void;
29
+ has(key: K): boolean;
30
+ get(key: K): V | undefined;
31
+ /**
32
+ * Allows to set multiple key-value pairs at once.
33
+ */
34
+ setMany(obj: Record<any, V>): this;
35
+ /**
36
+ * Insert or update. Keeps keys array sorted at all times.
37
+ * Returns this (Map-like).
38
+ */
39
+ set(key: K, value: V): this;
40
+ /**
41
+ * Delete by key. Returns boolean like Map.delete.
42
+ */
43
+ delete(key: K): boolean;
44
+ /**
45
+ * Iterables (Map-compatible), all in sorted order.
46
+ */
47
+ keys(): MapIterator<K>;
48
+ values(): MapIterator<V>;
49
+ entries(): MapIterator<[K, V]>;
50
+ [Symbol.iterator](): MapIterator<[K, V]>;
51
+ [Symbol.toStringTag]: string;
52
+ /**
53
+ * Zero-allocation callbacks over sorted data (faster than spreading to arrays).
54
+ */
55
+ forEach(cb: (value: V, key: K, map: Map<K, V>) => void, thisArg?: any): void;
56
+ firstKeyOrUndefined(): K | undefined;
57
+ firstKey(): K;
58
+ lastKeyOrUndefined(): K | undefined;
59
+ lastKey(): K;
60
+ firstValueOrUndefined(): V | undefined;
61
+ firstValue(): V;
62
+ lastValueOrUndefined(): V | undefined;
63
+ lastValue(): V;
64
+ firstEntryOrUndefined(): [K, V] | undefined;
65
+ firstEntry(): [K, V];
66
+ lastEntryOrUndefined(): [K, V] | undefined;
67
+ lastEntry(): [K, V];
68
+ toJSON(): Record<string, V>;
69
+ toObject(): Record<string, V>;
70
+ private getSortedKeys;
71
+ private sortKeys;
72
+ }
@@ -0,0 +1,184 @@
1
+ import { _assert } from '../error/index.js';
2
+ /**
3
+ * Maintains sorted array of keys.
4
+ * Sorts **data access**, not on insertion.
5
+ *
6
+ * @experimental
7
+ */
8
+ export class KeySortedMap2 {
9
+ map;
10
+ maybeSortedKeys;
11
+ keysAreSorted = false;
12
+ constructor(entries = [], opt = {}) {
13
+ this.#comparator = opt.comparator;
14
+ this.map = new Map(entries);
15
+ this.maybeSortedKeys = [...this.map.keys()];
16
+ }
17
+ #comparator;
18
+ /**
19
+ * Convenience way to create KeySortedMap from object.
20
+ */
21
+ static of(obj) {
22
+ return new KeySortedMap2(Object.entries(obj));
23
+ }
24
+ get size() {
25
+ return this.map.size;
26
+ }
27
+ clear() {
28
+ this.map.clear();
29
+ this.maybeSortedKeys.length = 0;
30
+ this.keysAreSorted = true;
31
+ }
32
+ has(key) {
33
+ return this.map.has(key);
34
+ }
35
+ get(key) {
36
+ return this.map.get(key);
37
+ }
38
+ /**
39
+ * Allows to set multiple key-value pairs at once.
40
+ */
41
+ setMany(obj) {
42
+ for (const [k, v] of Object.entries(obj)) {
43
+ this.map.set(k, v);
44
+ this.maybeSortedKeys.push(k);
45
+ }
46
+ this.keysAreSorted = false;
47
+ return this;
48
+ }
49
+ /**
50
+ * Insert or update. Keeps keys array sorted at all times.
51
+ * Returns this (Map-like).
52
+ */
53
+ set(key, value) {
54
+ if (!this.map.has(key)) {
55
+ this.maybeSortedKeys.push(key);
56
+ this.keysAreSorted = false;
57
+ }
58
+ this.map.set(key, value);
59
+ return this;
60
+ }
61
+ /**
62
+ * Delete by key. Returns boolean like Map.delete.
63
+ */
64
+ delete(key) {
65
+ if (!this.map.has(key))
66
+ return false;
67
+ this.map.delete(key);
68
+ // Delete operation keeps the array **as-is**, it may have been sorted or not.
69
+ const j = this.maybeSortedKeys.indexOf(key);
70
+ if (j !== -1)
71
+ this.maybeSortedKeys.splice(j, 1);
72
+ return true;
73
+ }
74
+ /**
75
+ * Iterables (Map-compatible), all in sorted order.
76
+ */
77
+ *keys() {
78
+ for (const key of this.getSortedKeys()) {
79
+ yield key;
80
+ }
81
+ }
82
+ *values() {
83
+ for (const key of this.getSortedKeys()) {
84
+ yield this.map.get(key);
85
+ }
86
+ }
87
+ *entries() {
88
+ for (const k of this.getSortedKeys()) {
89
+ yield [k, this.map.get(k)];
90
+ }
91
+ }
92
+ [Symbol.iterator]() {
93
+ return this.entries();
94
+ }
95
+ [Symbol.toStringTag] = 'KeySortedMap';
96
+ /**
97
+ * Zero-allocation callbacks over sorted data (faster than spreading to arrays).
98
+ */
99
+ forEach(cb, thisArg) {
100
+ const { map } = this;
101
+ for (const k of this.getSortedKeys()) {
102
+ cb.call(thisArg, map.get(k), k, this);
103
+ }
104
+ }
105
+ firstKeyOrUndefined() {
106
+ return this.getSortedKeys()[0];
107
+ }
108
+ firstKey() {
109
+ _assert(this.maybeSortedKeys.length, 'Map.firstKey called on empty map');
110
+ return this.getSortedKeys()[0];
111
+ }
112
+ lastKeyOrUndefined() {
113
+ if (!this.maybeSortedKeys.length)
114
+ return;
115
+ const keys = this.getSortedKeys();
116
+ return keys[keys.length - 1];
117
+ }
118
+ lastKey() {
119
+ const k = this.lastKeyOrUndefined();
120
+ _assert(k, 'Map.lastKey called on empty map');
121
+ return k;
122
+ }
123
+ firstValueOrUndefined() {
124
+ if (!this.maybeSortedKeys.length)
125
+ return;
126
+ return this.map.get(this.getSortedKeys()[0]);
127
+ }
128
+ firstValue() {
129
+ const v = this.firstValueOrUndefined();
130
+ _assert(v, 'Map.firstValue called on empty map');
131
+ return v;
132
+ }
133
+ lastValueOrUndefined() {
134
+ if (!this.maybeSortedKeys.length)
135
+ return;
136
+ const keys = this.getSortedKeys();
137
+ return this.map.get(keys[keys.length - 1]);
138
+ }
139
+ lastValue() {
140
+ const v = this.lastValueOrUndefined();
141
+ _assert(v, 'Map.lastValue called on empty map');
142
+ return v;
143
+ }
144
+ firstEntryOrUndefined() {
145
+ if (!this.maybeSortedKeys.length)
146
+ return;
147
+ const k = this.getSortedKeys()[0];
148
+ return [k, this.map.get(k)];
149
+ }
150
+ firstEntry() {
151
+ const e = this.firstEntryOrUndefined();
152
+ _assert(e, 'Map.firstEntry called on empty map');
153
+ return e;
154
+ }
155
+ lastEntryOrUndefined() {
156
+ if (!this.maybeSortedKeys.length)
157
+ return;
158
+ const keys = this.getSortedKeys();
159
+ const k = keys[keys.length - 1];
160
+ return [k, this.map.get(k)];
161
+ }
162
+ lastEntry() {
163
+ const e = this.firstEntryOrUndefined();
164
+ _assert(e, 'Map.lastEntry called on empty map');
165
+ return e;
166
+ }
167
+ toJSON() {
168
+ return this.toObject();
169
+ }
170
+ toObject() {
171
+ return Object.fromEntries(this.entries());
172
+ }
173
+ getSortedKeys() {
174
+ if (!this.keysAreSorted) {
175
+ return this.sortKeys();
176
+ }
177
+ return this.maybeSortedKeys;
178
+ }
179
+ sortKeys() {
180
+ this.maybeSortedKeys.sort(this.#comparator);
181
+ this.keysAreSorted = true;
182
+ return this.maybeSortedKeys;
183
+ }
184
+ }
@@ -16,4 +16,5 @@ export declare class Map2<K = any, V = any> extends Map<K, V> {
16
16
  setMany(obj: Record<any, V>): this;
17
17
  toObject(): Record<string, V>;
18
18
  toJSON(): Record<string, V>;
19
+ toString(): string;
19
20
  }
@@ -27,4 +27,7 @@ export class Map2 extends Map {
27
27
  toJSON() {
28
28
  return Object.fromEntries(this);
29
29
  }
30
+ toString() {
31
+ return `Map2(${this.size}) ${JSON.stringify(Object.fromEntries(this))}`;
32
+ }
30
33
  }
@@ -16,5 +16,5 @@ export declare class Set2<T = any> extends Set<T> {
16
16
  firstOrUndefined(): T | undefined;
17
17
  toArray(): T[];
18
18
  toJSON(): T[];
19
- get [Symbol.toStringTag](): string;
19
+ toString(): string;
20
20
  }
@@ -35,7 +35,7 @@ export class Set2 extends Set {
35
35
  toJSON() {
36
36
  return [...this];
37
37
  }
38
- get [Symbol.toStringTag]() {
39
- return 'Set';
38
+ toString() {
39
+ return `Set2(${this.size}) ${JSON.stringify([...this])}`;
40
40
  }
41
41
  }