@avstantso/std-ext 1.0.2 → 1.1.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/CHANGELOG.md +15 -0
- package/README.md +171 -4
- package/package.json +1 -1
- package/dist/array.d.ts +0 -42
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -26
- package/dist/index.js.map +0 -1
- package/dist/string.d.ts +0 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.1.0] - 2026-01-18
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `Object.definePropertyOnce` - adds property only if not exists
|
|
10
|
+
- `Object.definePropertiesOnce` - adds multiple properties only if not exist
|
|
11
|
+
- `RegExp.escape` - escape all RegExp special characters in a string (TC39 proposal polyfill)
|
|
12
|
+
- `Symbol.Has` - factory function to check and cast symbol in object
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- Reorganized extensions to `_std-ext/` directory structure
|
|
17
|
+
- `Array.prototype` and `String.prototype` extensions now use `definePropertiesOnce`
|
|
18
|
+
- `toCapitalized()` and `toUncapitalized()` now preserve string literal types
|
|
19
|
+
|
|
5
20
|
## [1.0.2] - 2026-01-15
|
|
6
21
|
|
|
7
22
|
### Fixed
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@avstantso/std-ext)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
|
|
6
|
-
Standard JavaScript objects extension with zero dependencies. Adds useful methods to native String and
|
|
6
|
+
Standard JavaScript objects extension with zero dependencies. Adds useful methods to native String, Array, RegExp, Object, and Symbol with full TypeScript support.
|
|
7
7
|
|
|
8
8
|
## Features
|
|
9
9
|
|
|
@@ -32,39 +32,173 @@ Simply import the package once in your application entry point to enable all ext
|
|
|
32
32
|
```typescript
|
|
33
33
|
import '@avstantso/std-ext';
|
|
34
34
|
|
|
35
|
-
// Now all
|
|
35
|
+
// Now all extensions are available
|
|
36
36
|
const greeting = "hello".toCapitalized(); // "Hello"
|
|
37
37
|
const items = [1, 0, 2, null, 3, false, 4].pack(); // [1, 2, 3, 4]
|
|
38
|
+
const escaped = RegExp.escape("file[1].txt"); // "file\\[1\\]\\.txt"
|
|
38
39
|
```
|
|
39
40
|
|
|
40
41
|
## API Reference
|
|
41
42
|
|
|
43
|
+
### RegExp Extensions
|
|
44
|
+
|
|
45
|
+
#### `RegExp.escape(str)`
|
|
46
|
+
|
|
47
|
+
Escapes all RegExp special characters in a string, making it safe to use in `new RegExp()`. This is a polyfill for the [TC39 RegExp.escape proposal](https://tc39.es/proposal-regex-escaping/).
|
|
48
|
+
|
|
49
|
+
**Parameters:**
|
|
50
|
+
- `str: string` - The string to escape
|
|
51
|
+
|
|
52
|
+
**Returns:** `string` - New string with escaped RegExp characters
|
|
53
|
+
|
|
54
|
+
**Escaped characters:** `\ ^ $ . * + ? ( ) [ ] { } |`
|
|
55
|
+
|
|
56
|
+
**Example:**
|
|
57
|
+
```typescript
|
|
58
|
+
RegExp.escape("file[1].txt"); // "file\\[1\\]\\.txt"
|
|
59
|
+
RegExp.escape("price: $10.00"); // "price: \\$10\\.00"
|
|
60
|
+
RegExp.escape("a+b=c?"); // "a\\+b=c\\?"
|
|
61
|
+
|
|
62
|
+
// Safe dynamic RegExp creation
|
|
63
|
+
const userInput = "test.file[1]";
|
|
64
|
+
const regex = new RegExp(RegExp.escape(userInput));
|
|
65
|
+
regex.test("test.file[1]"); // true
|
|
66
|
+
regex.test("testXfile11"); // false
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Object Extensions
|
|
70
|
+
|
|
71
|
+
#### `Object.definePropertyOnce(o, p, attributes)`
|
|
72
|
+
|
|
73
|
+
Adds a property to an object only if it doesn't already exist. Useful for polyfills and non-intrusive extensions. Especially handy for hot module replacement (HMR) environments like Vite, Webpack, or Parcel where modules may be re-executed multiple times.
|
|
74
|
+
|
|
75
|
+
**Parameters:**
|
|
76
|
+
- `o: T` - Object on which to add the property
|
|
77
|
+
- `p: PropertyKey` - The property name
|
|
78
|
+
- `attributes: PropertyDescriptor` - Descriptor for the property
|
|
79
|
+
|
|
80
|
+
**Returns:** `T` - The object passed in
|
|
81
|
+
|
|
82
|
+
**Example:**
|
|
83
|
+
```typescript
|
|
84
|
+
const obj = { existing: 1 };
|
|
85
|
+
|
|
86
|
+
Object.definePropertyOnce(obj, 'existing', { value: 999 });
|
|
87
|
+
Object.definePropertyOnce(obj, 'newProp', { value: 2 });
|
|
88
|
+
|
|
89
|
+
console.log(obj.existing); // 1 (unchanged)
|
|
90
|
+
console.log(obj.newProp); // 2 (added)
|
|
91
|
+
|
|
92
|
+
// Useful for polyfills
|
|
93
|
+
Object.definePropertyOnce(String.prototype, 'myMethod', {
|
|
94
|
+
value() { return this.toUpperCase(); }
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
#### `Object.definePropertiesOnce(o, properties)`
|
|
99
|
+
|
|
100
|
+
Adds multiple properties to an object, skipping any that already exist. Same HMR benefits as `definePropertyOnce`.
|
|
101
|
+
|
|
102
|
+
**Parameters:**
|
|
103
|
+
- `o: T` - Object on which to add the properties
|
|
104
|
+
- `properties: PropertyDescriptorMap` - Object containing property descriptors
|
|
105
|
+
|
|
106
|
+
**Returns:** `T` - The object passed in
|
|
107
|
+
|
|
108
|
+
**Example:**
|
|
109
|
+
```typescript
|
|
110
|
+
const obj = { a: 1 };
|
|
111
|
+
|
|
112
|
+
Object.definePropertiesOnce(obj, {
|
|
113
|
+
a: { value: 999 }, // skipped - already exists
|
|
114
|
+
b: { value: 2 }, // added
|
|
115
|
+
c: { value: 3 } // added
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
console.log(obj); // { a: 1, b: 2, c: 3 }
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Symbol Extensions
|
|
122
|
+
|
|
123
|
+
#### `Symbol.Has(symb, resultTemplate?)`
|
|
124
|
+
|
|
125
|
+
Creates a type guard function that checks if an object has a specific symbol property. Returns a reusable checker function with proper TypeScript type narrowing.
|
|
126
|
+
|
|
127
|
+
**Parameters:**
|
|
128
|
+
- `symb: symbol` - The symbol to check for
|
|
129
|
+
- `resultTemplate?: R` - Optional type-only argument to infer the symbol's value type (not used at runtime)
|
|
130
|
+
|
|
131
|
+
**Returns:** `(obj: unknown) => obj is { [K in Symb]: R }` - Type guard function
|
|
132
|
+
|
|
133
|
+
**Example:**
|
|
134
|
+
```typescript
|
|
135
|
+
const mySymbol = Symbol('mySymbol');
|
|
136
|
+
const hasMySymbol = Symbol.Has(mySymbol);
|
|
137
|
+
|
|
138
|
+
const obj1 = { [mySymbol]: 'value' };
|
|
139
|
+
const obj2 = { foo: 'bar' };
|
|
140
|
+
|
|
141
|
+
hasMySymbol(obj1); // true
|
|
142
|
+
hasMySymbol(obj2); // false
|
|
143
|
+
hasMySymbol(null); // false (safe with nullish values)
|
|
144
|
+
|
|
145
|
+
// Works great with well-known symbols
|
|
146
|
+
const hasIterator = Symbol.Has(Symbol.iterator);
|
|
147
|
+
|
|
148
|
+
hasIterator([]); // true
|
|
149
|
+
hasIterator('string'); // true
|
|
150
|
+
hasIterator(new Map()); // true
|
|
151
|
+
hasIterator({}); // false
|
|
152
|
+
|
|
153
|
+
// Type narrowing in conditionals
|
|
154
|
+
const data: unknown = getExternalData();
|
|
155
|
+
if (hasIterator(data)) {
|
|
156
|
+
for (const item of data) { ... } // TypeScript knows data is iterable
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// With typed value using resultTemplate
|
|
160
|
+
const symbolFreeze = Symbol('freeze');
|
|
161
|
+
const hasFreeze = Symbol.Has(symbolFreeze, () => {});
|
|
162
|
+
|
|
163
|
+
if (hasFreeze(obj)) {
|
|
164
|
+
obj[symbolFreeze](); // TypeScript knows it's () => void
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
42
168
|
### String Extensions
|
|
43
169
|
|
|
44
170
|
#### `toCapitalized()`
|
|
45
171
|
|
|
46
172
|
Capitalizes the first character of the string.
|
|
47
173
|
|
|
48
|
-
**Returns:** `Capitalize<string
|
|
174
|
+
**Returns:** `Capitalize<S>` where `S` is the string literal type
|
|
49
175
|
|
|
50
176
|
**Example:**
|
|
51
177
|
```typescript
|
|
52
178
|
"hello".toCapitalized(); // "Hello"
|
|
53
179
|
"world".toCapitalized(); // "World"
|
|
54
180
|
"".toCapitalized(); // ""
|
|
181
|
+
|
|
182
|
+
// Preserves literal types
|
|
183
|
+
const a = 'foo' as const;
|
|
184
|
+
const b = a.toCapitalized(); // type is 'Foo', not string
|
|
55
185
|
```
|
|
56
186
|
|
|
57
187
|
#### `toUncapitalized()`
|
|
58
188
|
|
|
59
189
|
Uncapitalizes the first character of the string.
|
|
60
190
|
|
|
61
|
-
**Returns:** `Uncapitalize<string
|
|
191
|
+
**Returns:** `Uncapitalize<S>` where `S` is the string literal type
|
|
62
192
|
|
|
63
193
|
**Example:**
|
|
64
194
|
```typescript
|
|
65
195
|
"Hello".toUncapitalized(); // "hello"
|
|
66
196
|
"World".toUncapitalized(); // "world"
|
|
67
197
|
"".toUncapitalized(); // ""
|
|
198
|
+
|
|
199
|
+
// Preserves literal types
|
|
200
|
+
const a = 'FOO' as const;
|
|
201
|
+
const b = a.toUncapitalized(); // type is 'fOO', not string
|
|
68
202
|
```
|
|
69
203
|
|
|
70
204
|
### Array Extensions
|
|
@@ -82,6 +216,39 @@ Filters out all falsy values from the array (false, 0, "", null, undefined, NaN)
|
|
|
82
216
|
[1, 0, 2, null, 3, false, 4].pack(); // [1, 2, 3, 4]
|
|
83
217
|
["a", "", "b", null, "c"].pack(); // ["a", "b", "c"]
|
|
84
218
|
[true, false, true, undefined].pack(); // [true, true]
|
|
219
|
+
|
|
220
|
+
// Real world example: React conditional CSS classes
|
|
221
|
+
function Button({ isActive, isDisabled, isLoading }) {
|
|
222
|
+
return (
|
|
223
|
+
<button
|
|
224
|
+
className={[
|
|
225
|
+
'btn',
|
|
226
|
+
isActive && 'btn--active',
|
|
227
|
+
isDisabled && 'btn--disabled',
|
|
228
|
+
isLoading && 'btn--loading'
|
|
229
|
+
].pack().join(' ')}
|
|
230
|
+
>
|
|
231
|
+
Click me
|
|
232
|
+
</button>
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Compare to the alternative without pack():
|
|
237
|
+
function Button({ isActive, isDisabled, isLoading }) {
|
|
238
|
+
return (
|
|
239
|
+
<button
|
|
240
|
+
className={`btn${
|
|
241
|
+
isActive ? ' btn--active' : ''
|
|
242
|
+
}${
|
|
243
|
+
isDisabled ? ' btn--disabled' : ''
|
|
244
|
+
}${
|
|
245
|
+
isLoading ? ' btn--loading' : ''
|
|
246
|
+
}`}
|
|
247
|
+
>
|
|
248
|
+
Click me
|
|
249
|
+
</button>
|
|
250
|
+
);
|
|
251
|
+
}
|
|
85
252
|
```
|
|
86
253
|
|
|
87
254
|
#### `peek()`
|
package/package.json
CHANGED
package/dist/array.d.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
declare namespace AVStantso.Array {
|
|
2
|
-
/**
|
|
3
|
-
* @summary Select `Array<T>` or `ReadonlyArray<T>` by `[W]ritable`
|
|
4
|
-
*/
|
|
5
|
-
type RW<W extends boolean, T = unknown> = W extends true ? Array<T> : ReadonlyArray<T>;
|
|
6
|
-
/**
|
|
7
|
-
* @summary Key of `AVStantso.Array.RW<W>`
|
|
8
|
-
*/
|
|
9
|
-
type Key<W extends boolean> = keyof {
|
|
10
|
-
[K in keyof RW<W> as `${Extract<K, string>}`]: 0;
|
|
11
|
-
};
|
|
12
|
-
namespace Key {
|
|
13
|
-
/**
|
|
14
|
-
* @summary Immutable array key
|
|
15
|
-
*/
|
|
16
|
-
type R = Key<false>;
|
|
17
|
-
/**
|
|
18
|
-
* @summary Mutable array key
|
|
19
|
-
*/
|
|
20
|
-
type RW = Key<true>;
|
|
21
|
-
/**
|
|
22
|
-
* @summary Mutation key only
|
|
23
|
-
*/
|
|
24
|
-
type W = Exclude<RW, R>;
|
|
25
|
-
}
|
|
26
|
-
interface Ex<W extends boolean, T> {
|
|
27
|
-
/**
|
|
28
|
-
* @summary Filter all not `falsy` items
|
|
29
|
-
* @returns New array with all not `falsy` items
|
|
30
|
-
*/
|
|
31
|
-
pack(): Array<T>;
|
|
32
|
-
/**
|
|
33
|
-
* @summary Get last item. Array not changes
|
|
34
|
-
* @returns Last item or `undefined`, if array empty. Array not changes
|
|
35
|
-
*/
|
|
36
|
-
peek(): T;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
declare interface ReadonlyArray<T> extends AVStantso.Array.Ex<false, T> {
|
|
40
|
-
}
|
|
41
|
-
declare interface Array<T> extends AVStantso.Array.Ex<true, T> {
|
|
42
|
-
}
|
package/dist/index.d.ts
DELETED
package/dist/index.js
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
Object.defineProperties(Array.prototype, {
|
|
4
|
-
pack: {
|
|
5
|
-
value() {
|
|
6
|
-
return this && this.filter((item) => item);
|
|
7
|
-
}
|
|
8
|
-
},
|
|
9
|
-
peek: {
|
|
10
|
-
value() {
|
|
11
|
-
return this && this.at(-1);
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
Object.defineProperty(String.prototype, 'toCapitalized', {
|
|
17
|
-
value() {
|
|
18
|
-
return this && this.charAt(0).toLocaleUpperCase() + this.slice(1);
|
|
19
|
-
}
|
|
20
|
-
});
|
|
21
|
-
Object.defineProperty(String.prototype, 'toUncapitalized', {
|
|
22
|
-
value() {
|
|
23
|
-
return this && this.charAt(0).toLocaleLowerCase() + this.slice(1);
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/array.ts","../src/string.ts"],"sourcesContent":["namespace AVStantso.Array {\n /**\n * @summary Select `Array<T>` or `ReadonlyArray<T>` by `[W]ritable`\n */\n export type RW<W extends boolean, T = unknown> = W extends true\n ? Array<T>\n : ReadonlyArray<T>;\n\n /**\n * @summary Key of `AVStantso.Array.RW<W>`\n */\n export type Key<W extends boolean> = keyof {\n [K in keyof RW<W> as `${Extract<K, string>}`]: 0;\n };\n\n export namespace Key {\n /**\n * @summary Immutable array key\n */\n export type R = Key<false>;\n\n /**\n * @summary Mutable array key\n */\n export type RW = Key<true>;\n\n /**\n * @summary Mutation key only\n */\n export type W = Exclude<RW, R>;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n export interface Ex<W extends boolean, T> {\n /**\n * @summary Filter all not `falsy` items\n * @returns New array with all not `falsy` items\n */\n pack(): Array<T>;\n\n /**\n * @summary Get last item. Array not changes\n * @returns Last item or `undefined`, if array empty. Array not changes\n */\n peek(): T;\n }\n}\n\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\ndeclare interface ReadonlyArray<T> extends AVStantso.Array.Ex<false, T> { }\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\ndeclare interface Array<T> extends AVStantso.Array.Ex<true, T> { }\n\nObject.defineProperties(Array.prototype, {\n pack: {\n value() {\n return this && this.filter((item: unknown) => item);\n }\n },\n peek: {\n value() {\n return this && this.at(-1);\n }\n }\n});\n","// eslint-disable-next-line @typescript-eslint/no-unused-vars\ndeclare interface String {\n toCapitalized(): Capitalize<string>;\n toUncapitalized(): Uncapitalize<string>;\n}\n\nObject.defineProperty(String.prototype, 'toCapitalized', {\n value() {\n return this && this.charAt(0).toLocaleUpperCase() + this.slice(1);\n }\n});\n\nObject.defineProperty(String.prototype, 'toUncapitalized', {\n value() {\n return this && this.charAt(0).toLocaleLowerCase() + this.slice(1);\n }\n});\n"],"names":[],"mappings":";;AAqDA,MAAM,CAAC,gBAAgB,CAAC,KAAK,CAAC,SAAS,EAAE;AACvC,IAAA,IAAI,EAAE;QACJ,KAAK,GAAA;AACH,YAAA,OAAO,IAAI,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC,IAAa,KAAK,IAAI,CAAC;QACrD;AACD,KAAA;AACD,IAAA,IAAI,EAAE;QACJ,KAAK,GAAA;YACH,OAAO,IAAI,IAAI,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;QAC5B;AACD;AACF,CAAA,CAAC;;AC1DF,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,SAAS,EAAE,eAAe,EAAE;IACvD,KAAK,GAAA;AACH,QAAA,OAAO,IAAI,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,iBAAiB,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IACnE;AACD,CAAA,CAAC;AAEF,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,SAAS,EAAE,iBAAiB,EAAE;IACzD,KAAK,GAAA;AACH,QAAA,OAAO,IAAI,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,iBAAiB,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IACnE;AACD,CAAA,CAAC;;"}
|
package/dist/string.d.ts
DELETED